Northwoods Software
©2008-2010 Northwoods Software Corporation
GoXam provides controls for implementing diagrams in your WPF and Silverlight applications. GoWPF is the name for the implementation of GoXam for WPF 3.5 or later; GoSilverlight is the name for the implementation of GoXam for Silverlight 3.0 or later.
You can find more documentation for GoXam in the installation kits. The site www.goxam.com has some online GoXam samples for both WPF and Silverlight. The sources for these samples are in the kits too. And you can ask questions and search for answers in the forum www.nwoods.com/forum or by e-mail to GoXam at our domain (nwoods.com).
This document assumes a reasonably good working knowledge of WPF or Silverlight. For overviews and introductions to these technologies, we suggest you first read several of the very good books about developing WPF or Silverlight applications and the web sites starting at:
· http://msdn.microsoft.com/en-us/library/aa970268.aspx
· http://windowsclient.net/wpf/default.aspx
This document also assumes a good working knowledge of .NET
programming, including generics and Linq for Objects and Linq for XML.
Table of Contents
Diagram Models and Data Binding
Discovering Relationships in the Data
Link information in the node data
Link information in separate link data
Group information in the node data
Group information with separate link data
Link Connection Points on Nodes
Collapsing and Expanding Trees
Collapsing and Expanding Groups
Groups as Independent Containers
Initial Positioning and Scaling
Decorative Elements and Unbound Parts
Saving and Loading data using XML
Saving and Loading Additional Data Properties
The Diagram class is a WPF or Silverlight Control that fully supports the standard customization features expected in WPF or Silverlight. These features include:
· styling
· templates
· data binding
· use of all WPF/Silverlight elements
· use of WPF/Silverlight layout
· animation
· commands
Diagrams consist of nodes that may be connected by links and that may be grouped together into groups. All of these parts are gathered together in layers and are arranged by layouts. Most parts are bound to data.
The Diagram control is data-bound to your application data in two manners:
· directly, in the DataTemplates that define the appearance and behavior of the nodes and links, and
· indirectly, via the diagram's Model property, which interprets your data to determine node-to-node link relationships and group-member relationships.
Each diagram has a PartManager that is responsible for creating a Node for each data item in the model's NodeSource data collection, and for creating or deleting Links as needed.
The nodes may be positioned manually (interactively or programmatically) or may be arranged by the diagram's Layout and by each Group’s Layout.
Tools handle mouse events. Each diagram has a number of tools that perform interactive tasks such as selecting parts or dragging them or drawing a new link between two nodes. The ToolManager determines which tool should be running, depending on the mouse events and current circumstances.
Each diagram also has a CommandHandler that implements various commands (such as Delete) and that handles keyboard events.
The DiagramPanel provides the ability to scroll the parts of the diagram or to zoom in or out. The DiagramPanel also contains all of the Layers, which in turn contain all of the Parts (Nodes and Links).
The Overview control allows the user to see the whole model and to control what part of it that the diagram displays.
The Palette control holds nodes that the user may drag-and-drop to a diagram.
You can select one or more parts in the diagram. The template implementation may change the appearance of the node or link when it is selected. The diagram may also add Adornments to indicate selection and to support tools such as resizing a node or reconnecting a link.
One of the principal features of XAML-defined presentation is the use of data binding. Practically all controls in the typical application will depend on data binding to get the information to be displayed, to be updated when the data changes, and to modify the data based on user input.
A Diagram control, however, must support more complex features than the typical control. The most complex standard controls inherit from ItemsControl, which will have a CollectionView to filter, sort, and group items into an ordered list. But unlike the data used by an ItemsControl, a diagram features relationships between data objects in ways more complex than a simple total ordering of items.
There are at least two important kinds of relationships that a diagram can support between data items:
· binary or ternary relationships forming a graph of nodes and links (or in similar terminology: nodes and arcs, or entities and relationships, or vertices and edges)
· grouping relationships, where a group contains members; perhaps for part/sub-part containment, or for the nesting of subgraphs
We make use of a model to discover, maintain, navigate, and modify these relationships based on the data that the diagram is bound to. Each Diagram has a model, but models can be shared between diagrams.
To be useful, every model needs to provide ways to do each of the following kinds of activities:
· getting the collection of data
· discovering the relationships in the data in order to build up the model
· updating the model when there are changes to the data
· examining the model and navigating the relationships
· modifying the collection of data, and changing their relationships
· notifying users of the model about changes to the model
· supporting undo and redo
· supporting data transfer
Some models are designed to be easier to use or to be more efficient when they have restrictions on the kinds of relationships they support. There are different ways of organizing the data. And you might or might not have any implementation flexibility in the classes used to implement the data, depending on your application requirements and whether you may modify your application’s data schema.
To achieve these goals we provide several model classes in the Northwoods.GoXam.Model namespace.
There are currently three primary model classes that implement the basic notion of being a diagram model.
The TreeModel is the simplest model. It is suitable for applications where the data forms a graph that is a tree structure: each node has at most one parent but may have any number of children, there are no undirected cycles or loops in the graph, and there is at most one link connecting any two nodes.
If your graph is not necessarily tree-structured, or if you want to support grouping as well as links, you will need to use either GraphModel or GraphLinksModel.
Use GraphModel when each node has a collection of references to the nodes that are connected to that node and are either coming from or going to the node. GraphModel permits cycles in the graph, including reflexive links where both ends of the link are the same node. However, there can be at most one link connecting each pair of nodes in a single direction, and there is no additional information associated with each link.
Grouping in GraphModel supports the membership of any node in at most one other node, and cycles in the grouping relationship are not permitted. Hence each subgraph is also a node, and node-subgraph membership forms its own tree-like structure.
If you need to support an arbitrary amount of data for each link, or if you need multiple distinct links connecting the same pair of nodes in the same direction, or if you need to connect links to links, you will need to use a separate data structure to represent each link. The GraphLinksModel takes a second data source that is the collection of link data.
GraphLinksModel also supports additional information at both ends of each link, so that one can implement logically different connection points for each node.
GraphLinksModel supports group-membership (i.e. subgraphs) in exactly the same manner that GraphModel does.
The model classes are generic classes. They are type parameterized by the type of the node data, NodeType, and by the type of the unique key, NodeKey, used as references to nodes. In the case of GraphLinksModel, there is also a type parameter for the link data type, LinkType, and a type parameter for optional data describing each end of the link, PortKey. (However, by default the implementation of diagram Nodes expects that PortKey must be a String.)
The model classes can probably be used with your existing application data classes. If you do not already have such data classes you can implement them by inheriting from the optional data classes that are in the Northwoods.GoXam.Model namespace, to add application-specific properties.
|
Generic Models |
Suggested data classes |
|
TreeModel <NodeType, NodeKey> |
TreeModelNodeData<NodeKey> |
|
GraphModel <NodeType, NodeKey> |
GraphModelNodeData<NodeKey> |
|
GraphLinksModel <NodeType, NodeKey, PortKey, LinkType> |
GraphLinksModelNodeData<NodeKey> and GraphLinksModelLinkData<NodeKey, PortKey> (or UniversalLinkData) |
|
|
|
The typical usage of models and data is:
// create a typed model
var model = new GraphLinksModel<MyData, String, String, MyLinkData>();
// maybe set other model properties too...
// specify the nodes, which includes the subgraph information
model.NodesSource = new ObservableCollection<MyData>() {
. . . // supply the node data
};
// specify the links between the nodes
model.LinksSource = new ObservableCollection<MyLinkData>() {
. . . // supply the link data
};
// have the Diagram use the new model
myDiagram.Model = model;
The node data and link data classes would be defined like:
public class MyData : GraphLinksModelNodeData<String> {
// define node data properties
}
public class MyLinkData : GraphLinksModelLinkData<String, String> {
// define link data properties
}
GraphModel and TreeModel do not have a LinksSource property and you would not need to define or use a link data class.
Each model needs access to the collection of data that it is modeling. This means setting the IDiagramModel.NodesSource property. The value must be a collection of node data.
For example, consider the following model initialization:
model.NodesSource = new List<String>() { "Alpha", "Beta", "Gamma", "Delta" };
This produces a graph without any links. The node data are just strings. Without any customized templates it might appear as:

If you want to be able to add or remove data from the NodesSource collection and have the model (and diagram) automatically updated, you should do the following:
model.NodesSource =
new ObservableCollection<String>() { "Alpha", "Beta", "Gamma", "Delta" };
ObservableCollection is in the System.Collections.ObjectModel namespace. It provides notifications when the collection is changed, so Adding a string to that ObservableCollection will cause an extra node to be created in the model and shown in the diagram.
Discovering Relationships in the Data
In order to build up the model’s knowledge of links between nodes, the model must examine each node data for link information, or it needs to be given link data describing the connections between the node data. Usually that information is stored as property values on the data, so you just need to provide those property names to the model. For generality, not only are simple property names supported, but also XAML-style property paths, typically property names separated by periods. Thus most model properties that specify property accessor paths have names that end in “Path”. An example is NodeKeyPath, which specifies how to get the key value for a node data object.
However, when the information is not accessible via a property path, perhaps because a method call is required or because the information needs to be computed, you can override protected virtual methods on the model to get the needed information. These discovery-implementation methods have names that start with “Find”. Because using a property path may use reflection, overriding these methods also produces an implementation that is faster and that is more likely to work in limited-permission environments, such as the typical Silverlight or XBAP application.
If you have the link relationship information stored on each node data, you might implement the node data class to have a property holding the name of the node and another property or two holding a collection of names that the node is connected to. This is how GraphModel expects the information to be organized.
If you don’t want to implement your own node data class, you can use one that we provide, GraphModelNodeData. This is a generic class, parameterized by the type of the key value. In the following examples, the keys are strings. We just need to specify the property names for discovering the “name” of each node and for discovering the collection of connected node names.
// model is a GraphModel using GraphModelNodeData<String> as the node data
// and strings as the node key type
var model = new GraphModel<GraphModelNodeData<String>, String>();
model.NodeKeyPath = "Key"; // use the GraphModelNodeData.Key property
model.ToNodesPath = "ToKeys"; // the node property to get a list of node keys
model.NodesSource = new ObservableCollection<GraphModelNodeData<String>>() {
new GraphModelNodeData<String>() {
Key="Alpha",
ToKeys=new ObservableCollection<String>() { "Beta", "Gamma" }
},
new GraphModelNodeData<String>() {
Key="Beta",
ToKeys=new ObservableCollection<String>() { "Beta" }
},
new GraphModelNodeData<String>() {
Key="Gamma",
ToKeys=new ObservableCollection<String>() { "Delta" }
},
new GraphModelNodeData<String>() {
Key="Delta",
ToKeys=new ObservableCollection<String>() { "Alpha" }
}
};
myDiagram.Model = model;
The result might appear as:

If you have the link data separate from the node data, as is the case for GraphLinksModel, you might do:
// model is a GraphLinksModel using strings as the node data
// and UniversalLinkData as the link data
var model = new GraphLinksModel<String, String, String, UniversalLinkData>();
// the key value for each node data is just the whole data itself, a String
model.NodeKeyPath = "";
model.NodeKeyIsNodeData = true; // NodeType and NodeKey values are the same!
model.LinkFromPath = "From"; // UniversalLinkData.From è source’s node key
model.LinkToPath = "To"; // UniversalLinkData.To è destination’s node key
model.NodesSource =
new ObservableCollection<String>() { "Alpha", "Beta", "Gamma", "Delta" };
model.LinksSource = new ObservableCollection<UniversalLinkData>() {
new UniversalLinkData("Alpha", "Beta"),
new UniversalLinkData("Alpha", "Gamma"),
new UniversalLinkData("Beta", "Beta"),
new UniversalLinkData("Gamma", "Delta"),
new UniversalLinkData("Delta", "Alpha")
};
myDiagram.Model = model;
Note that the node data in this example are just strings. Because the node value, a string, is also its own key value, there is no property to get the key given a node – hence the NodeKeyPath is the empty string. Of course in a “real” application you would have your own node data class, either inheriting from GraphLinksModelNodeData or defined from scratch. This would allow you to add all of the properties you need for each node, bindable from the node data templates.
In this example we are using the UniversalLinkData class that we provide as a convenient pre-defined class that you can use for representing link data. The From property of UniversalLinkData is supplied as the first argument of the constructor; it refers to the source node. The To property is supplied as the second argument; it refers to the destination node.
UniversalLinkData inherits from GraphLinksModelLinkData. As with the node data, the typical “real” application would define its own link data class, either inheriting from GraphLinksModelLinkData or defined from scratch, holding whatever information was needed for each link. Defining your own data classes is also more type-safe than using the “Universal…” classes that have properties of type Object.
The resulting diagram is exactly the same as for the previous example:

Grouping/membership information is accessible in a similar manner, as properties on the node data. You need to set two more model properties used for model discovery:
// model is a GraphModel or a GraphLinksModel
model.NodeIsGroupPath = "IsSubGraph"; // node property is true if it’s a group
model.GroupNodePath = "SubGraphKey"; // node property gets container’s name
Then change the NodesSource data as follows, initializing the two additional properties:
// model is a GraphModel using GraphModelNodeData<String> as the node data,
// and the node keys are strings
var model = new GraphModel<GraphModelNodeData<String>, String>();
model.NodeKeyPath = "Key"; // use the GraphModelNodeData.Key property
model.ToNodesPath = "ToKeys"; // this node property gets a list of node keys
model.NodeIsGroupPath = "IsSubGraph"; // node property is true if it’s a group
model.GroupNodePath = "SubGraphKey"; // node property gets container’s name
model.NodesSource= new ObservableCollection<GraphModelNodeData<String>>(){
new GraphModelNodeData<String>() {
Key="Alpha",
ToKeys=new ObservableCollection<String>() { "Beta", "Gamma" }
},
new GraphModelNodeData<String>() {
Key="Beta",
ToKeys=new ObservableCollection<String>() { "Beta" }
},
new GraphModelNodeData<String>() {
Key="Gamma",
ToKeys=new ObservableCollection<String>() { "Delta" },
SubGraphKey="Epsilon"
},
new GraphModelNodeData<String>() {
Key="Delta",
ToKeys=new ObservableCollection<String>() { "Alpha" },
SubGraphKey="Epsilon"
},
new GraphModelNodeData<String>() {
Key="Epsilon",
IsSubGraph=true
},
};
myDiagram.Model = model;
This results in a diagram that might look like:

The same result is easily achieved in a GraphLinksModel by using GraphLinksModelNodeData instead of GraphModelNodeData as the node data. In this example we will subclass GraphLinksModelNodeData in order to add a property for each node.
// model is a GraphLinksModel using MyData as the node data
// indexed with strings, and UniversalLinkData as the link data
var model = new GraphLinksModel<MyData, String, String, UniversalLinkData>();
model.NodeKeyPath = "Key"; // use the GraphLinksModelNodeData.Key property
model.LinkFromPath = "From"; // UniversalLinkData.From è source’s node key
model.LinkToPath = "To"; // UniversalLinkData.To è destination’s node key
model.NodeIsGroupPath = "IsSubGraph"; // node property is true if it’s a group
model.GroupNodePath = "SubGraphKey"; // node property gets container’s name
// specify the nodes, which includes subgraph information
// and other properties specific to MyData, such as Color
model.NodesSource = new ObservableCollection<MyData>() {
new MyData() { Key="Alpha", Color="Purple" },
new MyData() { Key="Beta", Color="Orange" },
new MyData() { Key="Gamma", Color="Red", SubGraphKey="Epsilon" },
new MyData() { Key="Delta", Color="Green", SubGraphKey="Epsilon" },
new MyData() { Key="Epsilon", Color="Blue", IsSubGraph=true },
};
// specify the links between the nodes
model.LinksSource = new ObservableCollection<UniversalLinkData>() {
new UniversalLinkData("Alpha", "Beta"),
new UniversalLinkData("Alpha", "Gamma"),
new UniversalLinkData("Beta", "Beta"),
new UniversalLinkData("Gamma", "Delta"),
new UniversalLinkData("Delta", "Alpha")
};
myDiagram.Model = model;
// Define custom node data; the node key is of type String
// Add a property named Color that might change
[Serializable] // serializable only for WPF
public class MyData : GraphLinksModelNodeData<String> {
public String Color {
get { return _Color; }
set {
if (_Color != value) {
String old = _Color;
_Color = value;
RaisePropertyChanged("Color", old, value);
}
}
}
private String _Color = "Black";
}
This model results in a diagram that looks the same as for the GraphModel above. (We’ll show how to use the new Color property soon.)

If you did not need to support updating the diagram when the value of Color changes, perhaps because you expect the data to be read-only, you could use a simpler implementation of the property:
// Define custom node data that does not notify the diagram about Color changes
[Serializable] // serializable only for WPF
public class MyData : GraphLinksModelNodeData<String> {
public MyData() {
this.Color = "Black";
}
public String Color { get; set; }
}
But if you do expect to modify the MyData.Color property and expect the corresponding node to change its appearance, you must use the more verbose definition shown earlier that calls RaisePropertyChanged in the setter.
Once you have created a model, initialized the model’s data (created a collection of data objects and set the model’s NodesSource), told it how to discover relationships between the nodes (set the various …Path properties of the model), and assigned the model to your Diagram, you might want to programmatically make changes to the diagram. You do this by making changes to the model and to the data, not by trying to change the Parts that are in the Diagram.
Here’s the code for creating a node and a link to that node, given a starting node:
// Given a Node, perhaps a selected one, or one that contains a button that
// was clicked, create another Node nearby and connect to it with a new link.
public Node ConnectToNewNode(Node start) {
MyData fromdata = start.Data as MyData;
if (fromdata == null) return null;
// all changes should always occur within a model transaction
myDiagram.StartTransaction("new connected node");
// create the new node data
MyData todata = new MyData();
// initialize the new node data here...
todata.Text = "new node";
todata.Location = new Point(start.Location.X + 250, start.Location.Y);
// add the new node data to the model's NodesSource collection
myDiagram.Model.AddNode(todata);
// add a link to the model connecting the two data objects
myDiagram.Model.AddLink(fromdata, null, todata, null);
// finish the transaction
myDiagram.CommitTransaction("new connected node");
return myDiagram.PartManager.FindNodeForData(todata, myDiagram.Model);
}
Whenever you modify a diagram programmatically, you should wrap the code in a transaction. StartTransaction and CommitTransaction are methods that you should call either on the Model or on the Diagram. (The Diagram’s methods just call the same named methods on the DiagramModel.) Although the primary benefit from using transactions is to group together side-effects for undo/redo, you should use model transactions even if you are not supporting undo/redo.
Note that you do not create a Node directly. Instead you create a data object corresponding to a node, initialize it, and then add it to the model’s NodesSource collection. It is most convenient to call the model’s AddNode method, but you could instead insert the data directly into the NodesSource collection, assuming the collection implements INotifyCollectionChanged.
Programmatically creating links uses the same idea: modify the model by adding or modifying data. For a GraphModel or a TreeModel, you create a link by setting a node data’s property or by adding a reference to a node data’s collection of references. For a GraphLinksModel you need to create a link data object and insert it into the model’s LinksSource collection. The AddLink method shown above may work for any kind of model, although for a GraphLinksModel there are some restrictions.
How does one get a reference to a node? If you can find the node data object, that’s all you need to get started with the process shown above. But if the code is being called from an event handler on an element in a Node, you will need to walk up the visual tree until you find the Node. The easiest way to do that is with:
Node node = Part.FindAncestor<Node>(sender as UIElement);
if (node != null) {
Node newnode = ConnectToNewNode(node);
if (newdata != null) {
// Select the new node
myDiagram.SelectedParts.Clear();
newnode.IsSelected = true;
}
}
The appearance of any node is determined not only by the data to which it is bound but also the DataTemplate used to define the elements of its visual tree.
The simplest useful data templates for nodes are probably:
<DataTemplate>
<TextBlock Text="{Binding Path=Data}" />
</DataTemplate>
or:
<DataTemplate>
<TextBlock Text="{Binding Path=Data.Key}" />
</DataTemplate>
The first one just converts the node’s data to a string and displays it; the second one converts the value of the node’s data’s Key property to a string and displays it. The first one is basically the one used in the screenshots shown before that used strings as the node data; the second one was used for those examples that used GraphModelNodeData or GraphLinksModelNodeData as the node data.
Because templates may be shared, and because it helps to simplify the XAML, you would normally use a node template by defining it as a resource and referring to it as the value of the NodeTemplate property of a Diagram. For example:
<UserControl.Resources>
<DataTemplate x:Key="NodeTemplate1">
<TextBlock Text="{Binding Path=Data.Key}" go:Part.SelectionAdorned="True" />
</DataTemplate>
<!-- define other templates here -->
</UserControl.Resources>
. . .
<go:Diagram x:Name="myDiagram" NodeTemplate="{StaticResource NodeTemplate1}" />
In this case the node will appear just as with the simpler templates, but when the user clicks on the node, a rectangular selection handle will be appear around the node, visually indicating that it is selected. In the following screenshot, “Alpha” and “Beta” are selected along with the link between them and the link connecting “Beta” with itself.

The node selection effect was achieved just by setting this attached property:
go:Part.SelectionAdorned="True"
Because Node inherits from Part, you can refer to precisely the same attached property with:
go:Node.SelectionAdorned="True"
Setting these kinds of attached properties has to be done on the root visual element of the data template, not on any nested element within that template.
In this section we will present more node data templates. These designs concentrate on simple nodes. A later section, “Ports on Nodes”, discusses the ability to have links connect to different elements on a node. Other sections discuss data templates for groups and for links.
|
You can find the XAML for the default templates and styles as: docs\GenericWPF.xaml or docs\GenericSilverlight.xaml. |
Let’s make use of the MyData.Color property. In this example each node will have a colored cube as the principal shape, with some text below it. First there needs to be a resource that is a converter from strings (the type of MyData.Color) to Brush:
<go:StringBrushConverter x:Key="theStringBrushConverter" />
Then we can bind each text’s Foreground value to the Brush returned by converting the MyData.Color property value.
<DataTemplate x:Key="NodeTemplate2">
<TextBlock Text="{Binding Path=Data.Key}"
Foreground="{Binding Path=Data.Color,
Converter={StaticResource theStringBrushConverter}}" />
</DataTemplate>
We now get the following screenshot:

Here’s a node template consisting of several text blocks surrounded by a border:
<DataTemplate x:Key="NodeTemplate3">
<Border BorderThickness="1" BorderBrush="Black"
Padding="2,0,2,0" CornerRadius="3"
go:Part.SelectionAdorned="True"
go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}">
<StackPanel>
<TextBlock Text="{Binding Path=Data.Name}" FontWeight="Bold" />
<TextBlock Text="{Binding Path=Data.Title}" />
<TextBlock Text="{Binding Path=Data.ID}" />
<TextBlock Text="{Binding Path=Data.Boss}" />
</StackPanel>
</Border>
</DataTemplate>
This results in nodes that look like these three (with an off-white background for the Diagram):

Note how the template is bound to the properties of the node data. Most of the bindings are one-way, from the data to the elements. But the binding between the Node.Location attached property and the data’s Location property is two-way: if the value of either property changes, the other one is updated. This means that not only will modifying the data’s Location property move the node in the diagram, but interactively dragging the node will modify the data.
For a different kind of node, we will use a DrawingImage (WPF only).
<DataTemplate x:Key="NodeTemplateDI">
<StackPanel go:Part.SelectionAdorned="True"
go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}">
<!-- WPF: This image uses a Drawing object for its source -->
<Image HorizontalAlignment="Center">
<Image.Source>
<DrawingImage PresentationOptions:Freeze="True">
<DrawingImage.Drawing>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<GeometryGroup>
<EllipseGeometry Center="50,50" RadiusX="45" RadiusY="20" />
<EllipseGeometry Center="50,50" RadiusX="20" RadiusY="45" />
</GeometryGroup>
</GeometryDrawing.Geometry>
<GeometryDrawing.Brush>
<LinearGradientBrush>
<GradientStop Offset="0.0" Color="Blue" />
<GradientStop Offset="1.0" Color="#CCCCFF" />
</LinearGradientBrush>
</GeometryDrawing.Brush>
<GeometryDrawing.Pen>
<Pen Thickness="10" Brush="Black" />
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
<TextBlock Text="{Binding Path=Data.Text}" HorizontalAlignment="Center" />
</StackPanel>
</DataTemplate>
The example DrawingImage was taken from the WPF documentation. This node template just adds a text label centered below the image. The result is:

Using a DrawingImage in WPF is more resource efficient when the drawing can be shared by multiple nodes.
Silverlight does not permit such sharing, but you can still get the same visual result by using a Path and the same GeometryGroup and LinearGradientBrush.
<DataTemplate x:Key="NodeTemplateEG">
<StackPanel go:Part.SelectionAdorned="True"
go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}">
<!-- Silverlight: use a Path -->
<Path Stroke="Black" StrokeThickness="10">
<Path.Fill>
<LinearGradientBrush>
<GradientStop Offset="0.0" Color="Blue" />
<GradientStop Offset="1.0" Color="#CCCCFF" />
</LinearGradientBrush>
</Path.Fill>
<Path.Data>
<GeometryGroup>
<EllipseGeometry Center="50,50" RadiusX="45" RadiusY="20" />
<EllipseGeometry Center="50,50" RadiusX="20" RadiusY="45" />
</GeometryGroup>
</Path.Data>
</Path>
<TextBlock Text="{Binding Path=Data.Text}" HorizontalAlignment="Center" />
</StackPanel>
</DataTemplate>
Of course you can make your templates as complex as you need and as pretty as you want. Because it is common to have each node display some kind of shape along with some text inside it, we have provided the NodePanel class which you can use with a NodeShape (WPF) or Path (Silverlight). (If you want the text to be outside of the shape, use a StackPanel or Grid to arrange the elements.)
Furthermore, we have implemented geometries for many common shapes. These are listed by the NodeFigure enumeration. By setting the go:NodePanel.Figure attached property on the NodeShape (WPF) or Path (Silverlight), the shape will automatically use a Geometry corresponding to that particular figure.
Consider the following two resource definitions:
<!-- define a conversion from String to Color -->
<go:StringColorConverter x:Key="theStringColorConverter" />
<DataTemplate x:Key="NodeTemplate4">
<!-- a NodePanel shows a background shape and
places the other panel children inside the shape -->
<go:NodePanel go:Node.SelectionAdorned="True"
go:Node.ToSpot="LeftSide" go:Node.FromSpot="RightSide" >
<!-- this shape gets the geometry defined by the NodePanel.Figure attached
property; in Silverlight, use a Path instead of a go:NodeShape -->
<go:NodeShape go:NodePanel.Figure="Database"
Stroke="Black" StrokeThickness="1">
<Shape.Fill>
<!-- use a fancier brush than a simple solid color -->
<LinearGradientBrush StartPoint="0.0 0.0" EndPoint="1.0 0.0">
<LinearGradientBrush.GradientStops>
<GradientStop Color="{Binding Path=Data.Color,
Converter={StaticResource theStringColorConverter}}"
Offset="0.0" />
<GradientStop Color="White" Offset="0.5" />
<GradientStop Color="{Binding Path=Data.Color,
Converter={StaticResource theStringColorConverter}}"
Offset="1.0" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Shape.Fill>
</go:NodeShape>
<!-- this TextBlock element is arranged inside the NodePanel’s shape -->
<TextBlock Text="{Binding Path=Data.Key}" TextAlignment="Center"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</go:NodePanel>
</DataTemplate>
Note how the LinearGradientBrush is constructed, binding two of the gradient stop colors to the MyData.Color property. [Note: this binding does not work in Silverlight.] The binding also depends on the presence of a StringColorConverter (not a StringBrushConverter), which was also defined as a resource. The result might look like:

The above use of NodePanel assumes that the shape, the first child of the panel, has a fixed width and height. (If the Width or Height are not supplied, they default to 100, as you can see in the “database” shapes above.) The other children of the NodePanel are arranged inside the first child, observing the HorizontalAlignment and/or VerticalAlignment properties of the child if the width and/or height are smaller than the available area inside the first child. For example:
<DataTemplate x:Key="NodeTemplate2">
<go:NodePanel Sizing="Fixed">
<!-- use a Path in Silverlight; use a go:NodeShape in WPF -->
<Path go:NodePanel.Figure="Parallelogram1"
Width="100" Height="50"
Stroke="Black" StrokeThickness="1" Fill="LightYellow" />
<TextBlock Text="{Binding Path=Data.Key}" TextAlignment="Left"
HorizontalAlignment="Right" VerticalAlignment="Bottom" />
</go:NodePanel>
</DataTemplate>
This might produce:

NodePanel.Sizing defaults to Fixed. Note the setting of Width and Height of the shape.
But you can also have the first child be sized to fit around the other children. This is convenient when you want to show a variable amount of text and want the minimal amount of shape surrounding it. Just set Sizing to Auto:
<DataTemplate x:Key="NodeTemplate3">
<go:NodePanel Sizing="Auto">
<!-- use a Path in Silverlight; use a go:NodeShape in WPF -->
<Path go:NodePanel.Figure="Parallelogram1"
Stroke="Black" StrokeThickness="1" Fill="LightYellow" />
<TextBlock Text="{Binding Path=Data.Key}" TextAlignment="Left"
Margin="10" />
</go:NodePanel>
</DataTemplate>
This might produce:

Note how we do not set the Width or Height of the shape. Furthermore we do not set the HorizontalAlignment or VerticalAlignment, because those properties have no effect. (TextAlignment affects how the text is rendered in its allotted space, not how it is positioned in its panel. Margin reserves some room around the TextBlock – without it the parallelogram would be tightly around the text.)
If you want to let users resize such nodes, you first need to think about which element is the “main” element that will control the size and layout of all of the other elements. The “main” element may very well not be the outermost or “root” visual element of the template. So it is not always sufficient to just set go: Part.Resizable="True" on the root element; you also need to indicate which element should be the one to get the resize handles and be resized by the ResizingTool:
<DataTemplate x:Key="NodeTemplate4">
<go:NodePanel Sizing="Fixed"
go:Part.Resizable="True" go:Part.SelectionElementName="Shape">
<go:NodeShape x:Name="Shape" go:NodePanel.Figure="Parallelogram1"
Width="100" Height="50"
Stroke="Black" StrokeThickness="1" Fill="LightYellow" />
<TextBlock Text="{Binding Path=Data.Key}" Margin="10"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</go:NodePanel>
</DataTemplate>
Note how the root element refers to the NodeShape (WPF) or Path (Silverlight) by name, so that user resizing will actually change the width and height of that shape. If you do not specify the Part.SelectionElementName, the root element will get the resize handles and attempts to resize the NodePanel are not likely to have the effect you want.
Sizing=”Fixed” is appropriate because that causes the NodePanel to fit the other child elements within the shape. Sizing=”Auto” causes NodePanel to fit the shape around all of the other children, which is not what the user would want if they were trying to resize it. In this case, if you want the user to interactively resize the node, you will need to have the Part.SelectionElementName refer to a different child of the NodePanel, not the first child.
When you want to let users modify the text in a node, one possibility is to implement your node’s DataTemplate to have its own TextBox that is normally Collapsed but that you make Visible when you want to edit. In fact, you can have arbitrarily complex controls in each of your nodes. However, the disadvantage is that all of those controls will always be created for each node, thereby increasing the overhead.
GoXam supports in-place text editing. Just set the Part.TextEditable attached property on a TextBlock.
<DataTemplate x:Key="NodeTemplate5">
<go:NodePanel Sizing="Auto">
<!-- use a Path in Silverlight; use a go:NodeShape in WPF -->
<Path go:NodePanel.Figure="Parallelogram1"
Stroke="Black" StrokeThickness="1" Fill="LightYellow" />
<TextBlock Text="{Binding Path=Data.Text, Mode=TwoWay}"
TextWrapping="Wrap" TextAlignment="Left" Margin="5"
go:Part.TextEditable="True" />
</go:NodePanel>
</DataTemplate>
(Note that since we expect the user to modify the text, we data bind the text to a different property on the data, not the unique Key.) If the user starts with:

Then if they select the node and then click on the text, the TextEditingTool brings up a TextBox. The user can edit the text. Losing focus by clicking elsewhere or by tabbing will accept the changes; typing ESCAPE will cancel the edit and restore the original string.

Here you can see the (blinking) cursor positioned at the end of the second line.
The simplest kind of link consists of only a line, perhaps consisting of multiple segments and curves. You must use the LinkShape element for this, in WPF:
<DataTemplate>
<!-- WPF -->
<go:LinkShape Stroke="Black" StrokeThickness="1" />
</DataTemplate>
In Silverlight, one must use the Path element and declare that the path is a “link shape”:
<DataTemplate>
<!-- Silverlight -->
<Path go:LinkPanel.IsLinkShape="True" Stroke="Black" StrokeThickness="1" />
</DataTemplate>
Like node templates, the typical pattern is to define templates as resources, and refer to them when initializing the Diagram:
<UserControl.Resources>
<DataTemplate x:Key="LinkTemplate1">
<go:LinkShape Stroke="Black" StrokeThickness="1" />
</DataTemplate>
<!-- define other templates here -->
</UserControl.Resources>
. . .
<go:Diagram x:Name="myDiagram" LinkTemplate="{StaticResource LinkTemplate1}" />
But note that such a link template will result in links for which there is no arrowhead nor any other decoration. Thus such a simple template can only be used where the links are not directional or where the direction is implicit in the diagram, such as in a tree.
It is more common to have at least an arrowhead on each link. For example, the following template is similar to the default link template – the one used when you do not specify the Diagram.LinkTemplate property.
<DataTemplate x:Key="LinkTemplate2">
<go:LinkPanel go:Part.SelectionElementName="Path"
go:Part.SelectionAdorned="True" >
<!-- in Silverlight substitute Path for go:LinkShape -->
<go:LinkShape x:Name="Path" go:LinkPanel.IsLinkShape="True"
Stroke="Black" StrokeThickness="1" />
<Polygon Fill="Black" Points="8 4 0 8 2 4 0 0" <!-- the arrowhead -->
go:LinkPanel.Alignment="MiddleRight" go:LinkPanel.Index="-1"
go:LinkPanel.Orientation="Along" />
</go:LinkPanel>
</DataTemplate>
Here is a visual representation of the points of the polygon:

This results in links that appear like those with arrowheads shown before:

Note the use of the LinkPanel class. A LinkPanel is a Panel that should have a LinkShape (WPF) or Path (Silverlight) in it named “Path”. The path’s Geometry is computed by the link’s Route – i.e. it is given a set of points so that the link shape appears to connect the link’s two nodes.
Once the link’s route is determined, the LinkPanel can arrange all of the other child elements of the panel to be somewhere along the path of the link. In this template, there is a Polygon that is acting as an arrowhead. There are three attached properties that control how a LinkPanel child such as this Polygon is positioned and rotated relative to the link shape. All three are used in this example.
· The LinkPanel.Alignment attached property is a Spot that indicates what point within the polygon should be positioned along the link path. (More about spots later.) In the above case the MiddleRight spot happens to be the point 8,4.
· The LinkPanel.Index attached property specifies at which segment the child element should be placed; zero means at the end near the “from” node, -1 means at the end near the “to” node.
· The LinkPanel.Orientation attached property controls whether and how the child element is rotated; “Along” means at the same angle as that link segment.
As a practical matter most link templates consist of a LinkPanel holding a LinkShape (WPF) or Path (Silverlight), and some varying number of decorations positioned along the link path.
Setting the Part.SelectionElementName attached property indicates which element should get a selection handle when the part becomes selected. In this case the link shape will get the selection handle, which is what you would normally want. If you did not set the SelectionElementName, the user would see a big Rectangle surrounding the whole link, which is probably not what you want.
You can have as many arrowheads as you like. For example, here’s a double-headed link:
<DataTemplate x:Key="LinkTemplate9">
<go:LinkPanel go:Part.SelectionElementName="Path">
<!-- in Silverlight substitute Path for go:LinkShape -->
<go:LinkShape x:Name="Path" go:LinkPanel.IsLinkShape="True"
Stroke="Black" StrokeThickness="1" />
<!-- the “to” arrowhead -->
<Polygon Fill="Black" Points="8 4 0 8 2 4 0 0"
go:LinkPanel.Alignment="1 0.5" go:LinkPanel.Index="-1"
go:LinkPanel.Orientation="Along" />
<!-- the “from” arrowhead -->
<Polyline Stroke="Black" StrokeThickness="1"
Points="7 0 0 3.5 7 7"
go:LinkPanel.Alignment="0 0.5" go:LinkPanel.Index="0"
go:LinkPanel.Orientation="Along" />
</go:LinkPanel>
</DataTemplate>
This might look like:

Of course link templates can be complicated too. If you are using a GraphLinksModel, you can bind to the link data. Let’s add a text element with a white background:
<DataTemplate x:Key="LinkTemplate3">
<go:LinkPanel go:Part.SelectionElementName="Path"
go:Part.SelectionAdorned="True">
<!-- in Silverlight substitute Path for go:LinkShape -->
<go:LinkShape x:Name="Path" go:LinkPanel.IsLinkShape="True"
Stroke="Black" StrokeThickness="1" />
<!-- the arrowhead -->
<Polygon Fill="Black" Points="8 4 0 8 2 4 0 0"
go:LinkPanel.Alignment="1 0.5" go:LinkPanel.Index="-1"
go:LinkPanel.Orientation="Along" />
<!-- when using a GraphLinksModel, bind to MyLinkData.Cost as a label -->
<StackPanel Background="White">
<TextBlock Text="{Binding Path=Data.Cost}" Foreground="Blue" />
</StackPanel>
</go:LinkPanel>
</DataTemplate>
This makes use of a MyLinkData type that you might define with a Cost property:
[Serializable] // serializable only for WPF
public class MyLinkData : GraphLinksModelLinkData<String, String> {
public double Cost {
get { return _Cost; }
set {
if (_Cost != value) {
double old = _Cost;
_Cost = value;
RaisePropertyChanged("Cost", old, value);
}
}
}
private double _Cost;
}
If you expect the link data not to change and need to update the diagram, you could have a simpler implementation of the link data class:
[Serializable] // serializable only for WPF
public class MyLinkData : GraphLinksModelLinkData<String, String> {
public double Cost { get; set; } // if setter does not need to notify
}
The result might look like:

The example above performed data binding of a TextBlock’s Text property to a property on the Link’s Data (an instance of MyLinkData). However, it is also possible to data bind link properties to properties on either of the Link’s connected Nodes. This will work even if the model does not support separate link data.
For instance, if you want each link to be colored according to some property of the “To” node, you can bind the Stroke to {Binding Path=Link.ToData.SomeProperty, Converter={StaticResource someConverter}}.
For example, we can customize the link colors of the DoubleTree sample by changing the link’s template to depend on the Info.LayoutId property, where Info is a node data class defined in that sample, and where the LayoutId property indicates which direction the tree is growing at that node.
<local:LinkBrushConverter x:Key="theLinkBrushConverter" />
<DataTemplate x:Key="LinkTemplate">
<go:LinkPanel>
<!-- this is Silverlight; use go:LinkShape in WPF instead of Path -->
<Path go:LinkPanel.IsLinkShape="True" StrokeThickness="1"
Stroke="{Binding Path=Link.ToData.LayoutId,
Converter={StaticResource theLinkBrushConverter}}" />
<Polygon Fill="{Binding Path=Link.ToData.LayoutId,
Converter={StaticResource theLinkBrushConverter}}"
Points="8 4 0 8 2 4 0 0" go:LinkPanel.Index="-1"
go:LinkPanel.Alignment="1 0.5" go:LinkPanel.Orientation="Along" />
</go:LinkPanel>
</DataTemplate>
Note that in this example both the Path.Stroke and the Polygon.Fill are bound to the same data property using the same converter.
The LinkBrushConverter needs to convert the string value of Info.LayoutId to the desired Brush. This is an example of defining your own custom data converter:
public class LinkBrushConverter : Northwoods.GoXam.Converter {
public override object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture) {
if (value is String) {
switch ((String)value) {
case "Right": return Black;
case "Left": return Red;
case "Up": return Green;
case "Down": return Blue;
default: return Black;
}
}
return Black;
}
private static Brush Black = new SolidColorBrush(Colors.Black);
private static Brush Red = new SolidColorBrush(Colors.Red);
private static Brush Green = new SolidColorBrush(Colors.Green);
private static Brush Blue = new SolidColorBrush(Colors.Blue);
}
For efficiency this example converter only returns one of four predefined solid brushes that are shared. However, it is common to return a new SolidColorBrush when the color is more variable. In any case, this is what the results might look like:

So far all of the example links have been fairly simple. If you want to customize the path that each link takes, you need to set properties on the link’s Route. Each Link has a Route that it creates by default, but you can replace it with one that you have initialized.
<DataTemplate x:Key="LinkTemplate4">
<go:LinkPanel go:Part.SelectionElementName="Path"
go:Part.SelectionAdorned="True">
<go:Link.Route>
<go:Route Routing="Orthogonal" />
</go:Link.Route>
<!-- in Silverlight substitute Path for go:LinkShape -->
<go:LinkShape x:Name="Path" go:LinkPanel.IsLinkShape="True"
Stroke="Black" StrokeThickness="1" />
<Polygon Fill="Black" Points="8 4 0 8 2 4 0 0"
go:LinkPanel.Alignment="1 0.5" go:LinkPanel.Index="-1"
go:LinkPanel.Orientation="Along" />
</go:LinkPanel>
</DataTemplate>
The Route.Routing property controls what general route the link will take. The default value is LinkRouting.Normal, which produces the direct paths you have seen so far. But if you use LinkRouting.Orthogonal, which tries to make each segment of the link either horizontal or vertical, it might look like:

Another routing option assumes orthogonal segments for the link, but also tries to avoid crossing over other nodes.
<go:Link.Route>
<go:Route Routing="AvoidsNodes" />
</go:Link.Route>
After adding two nodes to be in the way:

The Route.Curve property specifies what kind of path to draw given the points calculated for the route. The default value is LinkCurve.None, which produces the straight line segments you have seen in the examples so far. The LinkCurve.Bezier value produces naturally curved paths.
<go:Link.Route>
<go:Route Curve="Bezier" />
</go:Link.Route>

You can control the amount of curvature by setting the Route.Curviness property. With varying numbers of links between the same pair of nodes it will automatically compute values for Curviness unless you assign it explicitly.



Combining orthogonal Routing and Corner:
<go:Link.Route>
<go:Route Routing="Orthogonal" Corner="10" />
</go:Link.Route>
produces:

Or use Curve.JumpOver with LinkRouting.Orthogonal or AvoidsNodes:
<go:Link.Route>
<go:Route Routing="Orthogonal" Curve="JumpOver" Corner="10" />
</go:Link.Route>

It is common to add annotations or decorations to links, particularly text. You can easily add any elements you want to a LinkPanel. For example, let us add three text labels to a link, one in the middle, one on the left side of the link and one on the right side of the link:
<DataTemplate x:Key="LinkTemplate5">
<go:LinkPanel>
<go:LinkShape Stroke="Black" StrokeThickness="1" />
<Polygon Fill="Black" Points="8 4 0 8 2 4 0 0" go:LinkPanel.Index="-1"
go:LinkPanel.Alignment="1 0.5" go:LinkPanel.Orientation="Along" />
<TextBlock Text="Left"
go:LinkPanel.Offset="0 -10" go:LinkPanel.Orientation="Upright" />
<TextBlock Text="Middle"
go:LinkPanel.Offset="0 0" go:LinkPanel.Orientation="Upright" />
<TextBlock Text="Right"
go:LinkPanel.Offset="0 10" go:LinkPanel.Orientation="Upright" />
</go:LinkPanel>
</DataTemplate>
The LinkPanel.Offset attached property controls where to position the element relative to a point on a segment of the link. A positive value for the Y offset moves the label element towards the right side of the link, as seen going in the direction of the link. Naturally a negative value for the Y offset moves it towards the left side.
The segment is specified by the LinkPanel.Index attached property, which defaults to the middle of the whole link. The offset is rotated according to the angle formed by that link segment. Here are the results, with the nodes at different relative positions to demonstrate how the labels follow the (only) segment of the link.


The LinkPanel.Orientation attached property controls the angle of the
label relative to the angle of the link segment. The value of Along, as
you have seen above with arrowheads, results in a label angle that is the same
as the segment’s angle. The value of Upright is useful for elements
containing text because the text will not be upside down, although like Along
it will always be angled to follow the link. To continue the counter-clockwise
rotation of the Beta node around the Alpha node:


When you specify the LinkPanel.Index, you can position labels at places
other than the middle of the link. The index of zero is at the very beginning
of the link; a value of one is at the next point in the route. Negative values
are permitted – they count down from the “to” end of the link, with index -1 at
the very last point of the link.
<TextBlock Text="From" go:LinkPanel.Index="0"
go:LinkPanel.Offset="NaN NaN" go:LinkPanel.Orientation="Upright" />
<TextBlock Text="To" go:LinkPanel.Index="-1"
go:LinkPanel.Offset="NaN NaN" go:LinkPanel.Orientation="Upright" />


The uses of NaN in the Offset mean half the width and half the height of the label element, which is convenient when the size of the label element may vary.
Links need not be straight with a single segment. Here are examples of Orthogonal routing and of Bezier curves, with the middle label having two lines of text:


Labels need not be TextBlocks. The default LinkPanel.Orientation is None, meaning that the label element is not rotated at all. For example:
<!-- LinkPanel labels in Silverlight -->
<go:NodePanel go:LinkPanel.Index="0" go:LinkPanel.Offset="5 5" >
<Path go:NodePanel.Figure="EightPointedStar" Fill="Red"
Width="10" Height="10" />
</go:NodePanel>
<Button Content="?" Click="Button_Click" />
<!-- LinkPanel labels in WPF -->
<go:NodeShape go:LinkPanel.Index="0" go:LinkPanel.Offset="5 5"
go:NodePanel.Figure="EightPointedStar" Fill="Red"
Width="10" Height="10" />
<Button Content="?" Click="Button_Click" />

In the examples above you have seen how each link will end at the edge of the node. To illustrate this further, notice in the following screenshot where the arrowheads appear to terminate around the “Alpha” node, around the rectangular bounds of the text:

If the node is not shaped like a rectangle, the link will connect at the edge.
<DataTemplate x:Key="NodeTemplate1">
<go:NodePanel go:Node.SelectionAdorned="True">
<go:NodeShape go:NodePanel.Figure="OrGate" Width="70" Height="70"
Stroke="Black" StrokeThickness="1"
Fill="{Binding Path=Data.Color,
Converter={StaticResource theStringBrushConverter}}" />
<TextBlock Text="{Binding Path=Data.Key}" TextAlignment="Center"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</go:NodePanel>
</DataTemplate>

But what if you want to limit the points at which links may connect to a node? You can do so by setting the Node.FromSpot and Node.ToSpot attached properties on the root visual element of the node. The default value is Spot.None, which means to calculate a point along the edge of the element. But you can specify spot values that describe particular positions on the element. For example:
<DataTemplate x:Key="NodeTemplate2">
<TextBlock Text="{Binding Path=Data.Key}" go:Node.SelectionAdorned="True"
go:Node.ToSpot="MiddleLeft" go:Node.FromSpot="MiddleRight" />
</DataTemplate>
This specifies that links coming into this node connect at the middle of the left side, and that links going out of this node connect at the middle of the right side. Such a convention is appropriate for diagrams that have a general sense of direction to them, such as the following one which goes from left to right:

You can also specify that the links go into a node not at a single spot but spread out along one side. Change the previous example to use:
go:Node.ToSpot="LeftSide" go:Node.FromSpot="RightSide"
And you will get:

Of course specifying a side works well only for nodes that are basically rectangular and probably larger than in this case. So let’s add a border around the text to make each node bigger:
<DataTemplate x:Key="NodeTemplate3">
<Border BorderBrush="Black" BorderThickness="1" Padding="3"
go:Node.SelectionAdorned="True"
go:Node.ToSpot="LeftSide" go:Node.FromSpot="RightSide" >
<TextBlock Text="{Binding Path=Data.Key}" />
</Border>
</DataTemplate>
Note how the attached node properties have been moved to the new root element of the data template. This node template with the same data results in:

Of course you can use different kinds of Routes for the link template. Consider:
<go:Link.Route>
<go:Route Curve="Bezier" />
</go:Link.Route>

Or:
<go:Link.Route>
<go:Route Routing="Orthogonal" Corner="10" />
</go:Link.Route>

Although you have some control over where links will connect at a node (at a particular spot, along one or more sides, or at the intersection with the edge), there are times when you want to have different logical and graphical places at which links should connect. The elements to which a link may connect are called ports. There may be any number of ports in a node. By default there is just one port, the root visual element, which results in the effect of having the whole node act as the port, as you have seen above. Support for multiple ports is only possible in a GraphLinksModel because only when you have separate data for each link can you attach information describing which port the link should connect to.
To declare that a particular element is a port, set the Node.PortId attached property on it. Unlike most of the Part and Node attached properties, which may only be applied to the root visual element of the node, the port-related Node attached properties may apply to any element in the visual tree of the node. These attached properties have names that start with “Port”, “From”, “To”, or “Linkable”.
<DataTemplate x:Key="NodeTemplate4">
<Border BorderBrush="Black" BorderThickness="1"
go:Node.SelectionAdorned="True">
<Grid Background="LightGray">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="3"
Text="{Binding Path=Data.Key}" TextAlignment="Center"
FontWeight="Bold" TextWrapping="Wrap" Margin="4,4,4,2" />
<StackPanel Grid.Column="0" Grid.Row="1" Orientation="Horizontal">
<!-- this Rectangle is a port, identified with the string “A”;
links only come into it at the middle of the left side -->
<Rectangle Width="6" Height="6" Fill="Black"
go:Node.PortId="A" go:Node.ToSpot="MiddleLeft" />
<TextBlock Text="A" />
</StackPanel>
<StackPanel Grid.Column="0" Grid.Row="2" Orientation="Horizontal">
<!-- this Rectangle is another input port, named “B” -->
<Rectangle Width="6" Height="6" Fill="Black"
go:Node.PortId="B" go:Node.ToSpot="MiddleLeft" />
<TextBlock Text="B" />
</StackPanel>
<StackPanel Grid.Column="2" Grid.Row="1" Grid.RowSpan="2"
Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="Out" />
<!-- this Rectangle is another port, identified with the string “Out”;
links only go out of it at the middle of the right side -->
<Rectangle Width="6" Height="6" Fill="Black"
go:Node.PortId="Out" go:Node.FromSpot="MiddleRight" />
</StackPanel>
</Grid>
</Border>
</DataTemplate>
Each port has a Node.PortId that corresponds to the optional port parameter information at both ends of each link. To avoid visual confusion in this example there is also a TextBlock next to each port, showing the same string.
This node template, combined with a GraphLinksModel and data such as:
var model = new GraphLinksModel<MyData, String, String, MyLinkData>();
model.NodesSource = new ObservableCollection<MyData>() {
new MyData() { Key="Add1" },
new MyData() { Key="Add2"},
new MyData() { Key="Subtract" },
};
model.LinksSource = new ObservableCollection<MyLinkData>() {
new MyLinkData() { From="Add1", FromPort="Out", To="Subtract", ToPort="A" },
new MyLinkData() { From="Add2", FromPort="Out", To="Subtract", ToPort="B" },
};
myDiagram.Model = model;
can produce a diagram like:

A common technique for simplifying tree-structured graphs is to collapse subtrees. One way to implement this functionality is to add a Button to each node.
<!-- show either a "+" or a "-" as the Button content -->
<go:BooleanStringConverter x:Key="theButtonConverter"
TrueString="-" FalseString="+" />
<DataTemplate x:Key="NodeTemplate">
<StackPanel Orientation="Horizontal" go:Part.SelectionAdorned="True"
go:Node.IsTreeExpanded="False">
<!-- go:Node.IsTreeExpanded="False" tells the node to start collapsed -->
<go:NodePanel Sizing="Auto">
<go:NodeShape go:NodePanel.Figure="Ellipse"
Fill="{Binding Path=Data.Color,
Converter={StaticResource theBrushConverter}}" />
<TextBlock Text="{Binding Path=Data.Color}" />
</go:NodePanel>
<Button x:Name="myCollapseExpandButton" Click="CollapseExpandButton_Click"
Content="{Binding Path=Node.IsExpandedTree,
Converter={StaticResource theButtonConverter}}"
Width="20" />
</StackPanel>
</DataTemplate>
Note that the Button.Content is bound to the Node.IsExpandedTree property, via a converter that converts the boolean value to either the string “+” or the string “-“. Of course you can (and probably should) style the Button the way you want instead of using those two text strings. But we’ll keep it simple in this document.
The Button.Click event handler might be implemented as:
private void CollapseExpandButton_Click(object sender, RoutedEventArgs e) {
// the Button is in the visual tree of a Node
Button button = (Button)sender;
Node n = Part.FindAncestor<Node>(button);
if (n != null) {
SimpleData parentdata = (SimpleData)n.Data;
// always make changes within a transaction
myDiagram.StartTransaction("CollapseExpand");
// toggle whether this node is expanded or collapsed
n.IsExpandedTree = !n.IsExpandedTree;
myDiagram.CommitTransaction("CollapseExpand");
}
}
A graph might start with a single node:

An expansion (and a control-mouse-wheel zoom-out) might produce:

Further expansions (and zoom outs) might produce:

To define the appearance of group nodes, you can set the Diagram.GroupTemplate property. The default template produces the following simple representation of a group, in this case “Epsilon”.

To customize the appearance of a group, you could define a template such as:
<DataTemplate x:Key="GroupTemplate1">
<StackPanel go:Node.LocationElementName="myGroupPanel">
<!-- This is the “header” for the group -->
<TextBlock x:Name="Label" Text="{Binding Path=Data.Key}"
FontSize="18" FontWeight="Bold" Foreground="Green"
HorizontalAlignment="Center"/>
<Border x:Name="myBorder" CornerRadius="5"
BorderBrush="Green" BorderThickness="2">
<!-- The GroupPanel is the placeholder for member parts -->
<go:GroupPanel x:Name="myGroupPanel" Padding="5" />
</Border>
<!-- This is some extra information for the group -->
<TextBlock Text="BottomRight" HorizontalAlignment="Right" />
</StackPanel>
</DataTemplate>

Notice that there is a GroupPanel element inside the Border. You use a GroupPanel as the placeholder for all of the nodes and links that are members of the group. The member Nodes and Links are not visual children of the panel or of the group node – they are independent parts in the diagram.
However, GroupPanel is a Panel, so it can have its own children, which cannot be nodes or links. In fact, GroupPanel is a SpotPanel, which is like a Canvas, except that it uses Spots for positioning the children.
If you use a GroupPanel, and if it is not the root visual element of the data template, it must be named as the Node.LocationElementName for the group. Just give the GroupPanel a Name and refer to it via the attached property Node.LocationElementName on the root element. This means that the Node’s location will always be the same as the GroupPanel’s location, even as elements outside of the GroupPanel change size or move around with respect to the panel.
<DataTemplate x:Key="GroupTemplate2">
<Border x:Name="myBorder" CornerRadius="5"
BorderBrush="Green" BorderThickness="2"
go:Node.LocationElementName="myGroupPanel">
<go:GroupPanel x:Name="myGroupPanel" Padding="5,20,5,10">
<TextBlock Text="{Binding Path=Data.Key}"
go:SpotPanel.Spot="MiddleTop"
go:SpotPanel.Alignment="MiddleTop"
FontSize="16" FontWeight="Bold" Foreground="Green"/>
<TextBlock Text="BottomRight" FontSize="7"
go:SpotPanel.Spot="BottomRight"
go:SpotPanel.Alignment="BottomRight" />
</go:GroupPanel>
</Border>
</DataTemplate>
This GroupPanel has two text children in addition to the members that are its “virtual” children. Those TextBlocks are positioned using the two attached properties: SpotPanel.Spot and SpotPanel.Alignment. Note that the Padding leaves extra room for the labels inside the border so that the nodes won’t overlap with them.

The second screenshot shows the result of dragging the “Gamma” node downward a bit.

An alternative implementation would use a StackPanel or Grid to position the text outside of the GroupPanel, with a Border surrounding the StackPanel. This would have the advantage of having the text inside the border, no matter how small the GroupPanel got.
It is common to simplify graphs by collapsing subgraphs into a single node. One way to implement collapsible subgraphs is with a button.
<!-- show either a "+" or a "-" as the Button content -->
<go:BooleanStringConverter x:Key="theButtonConverter"
TrueString="-" FalseString="+" />
<DataTemplate x:Key="GroupTemplate">
<Border CornerRadius="5" BorderThickness="2" Background="Transparent"
BorderBrush="{Binding Path=Data.Color,
Converter={StaticResource theBrushConverter}}"
go:Part.SelectionAdorned="True"
go:Node.LocationElementName="myGroupPanel"
go:Group.IsSubGraphExpanded="False">
<!-- go:Group.IsSubGraphExpanded="False" causes it to start collapsed -->
<StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<Button x:Name="myCollapseExpandButton"
Click="CollapseExpandButton_Click"
Content="{Binding Path=Group.IsExpandedSubGraph,
Converter={StaticResource theButtonConverter}}"
Width="20" Margin="0 0 5 0"/>
<TextBlock Text="{Binding Path=Data.Key}" FontWeight="Bold" />
</StackPanel>
<go:GroupPanel x:Name="myGroupPanel" Padding="5" />
</StackPanel>
<!-- each Group can have its own Layout -->
<go:Group.Layout>
<!-- this Layout is performed whenever any nested Group changes size -->
<go:LayeredDigraphLayout Direction="90"
Conditions="Standard GroupSizeChanged" />
</go:Group.Layout>
</Border>
</DataTemplate>
Note that the Button.Content is bound to the Group.IsExpandedSubGraph property, via a converter that converts the boolean value to either the string “+” or the string “-“.
Collapsed it might appear as:

The Button.Click event handler might be defined as:
private void CollapseExpandButton_Click(object sender, RoutedEventArgs e) {
// the Button is in the visual tree of a Node
Button button = (Button)sender;
Group sg = Part.FindAncestor<Group>(button);
if (sg != null) {
SimpleData subgraphdata = (SimpleData)sg.Data;
// always make changes within a transaction
myDiagram.StartTransaction("CollapseExpand");
// toggle whether this node is expanded or collapsed
sg.IsExpandedSubGraph = !sg.IsExpandedSubGraph;
myDiagram.CommitTransaction("CollapseExpand");
}
}
Expanded it might look like:

A more “standard” implementation for a Group might use an Expander:
<DataTemplate x:Key="GroupTemplate6">
<Expander Header="{Binding Path=Data.Name}"
IsExpanded="{Binding Path=Group.IsExpandedSubGraph, Mode=TwoWay}"
go:Node.LocationElementName="myGroupPanel">
<Border BorderBrush="Green" BorderThickness="2"
Background="Transparent" CornerRadius="5">
<go:GroupPanel x:Name="myGroupPanel" Padding="6" />
</Border>
</Expander>
</DataTemplate>
Note how the Expander.IsExpanded property is data-bound to Group.IsExpandedSubGraph.
|
|
|
|
The SpotPanel.Spot attached property specifies where the element should be positioned in the GroupPanel. The SpotPanel.Alignment attached property specifies what point of the element should be positioned at the SpotPanel.Spot point.
Although previous examples have used standard named values such as Spot.BottomRight and Spot.MiddleLeft, spots are more general than that. A spot represents a relative point from (0,0) to (1,1) within a rectangle from the top-left corner to the bottom-right corner, plus an absolute offset. Here’s a demonstration showing nine text objects positioned at the standard nine spots:
<go:SpotPanel>
<Rectangle go:SpotPanel.Main="True" Fill="LightCoral"
Width="200" Height="100" />
<TextBlock go:SpotPanel.Spot="0.0 0.0" Text="0 0" />
<TextBlock go:SpotPanel.Spot="0.5 0.0" Text="0.5 0" />
<TextBlock go:SpotPanel.Spot="1.0 0.0" Text="1 0" />
<TextBlock go:SpotPanel.Spot="0.0 0.5" Text="0 0.5" />
<TextBlock go:SpotPanel.Spot="0.5 0.5" Text="0.5 0.5" />
<TextBlock go:SpotPanel.Spot="1.0 0.5" Text="1 0.5" />
<TextBlock go:SpotPanel.Spot="0.0 1.0" Text="0 1" />
<TextBlock go:SpotPanel.Spot="0.5 1.0" Text="0.5 1" />
<TextBlock go:SpotPanel.Spot="1.0 1.0" Text="1 1" />
</go:SpotPanel>
The Main attached property says that the spots are all relative to the bounds of the first child element of the SpotPanel, which in this case is a Rectangle. By default the center of each element is aligned at the spot point.

Instead of always centering the element at the spot point, you can use any other spot in that element. The following three child elements are all positioned at the same (0, 0) spot, but with different alignments.
<go:SpotPanel>
<Rectangle go:SpotPanel.Main="True" Fill="LightCoral"
Width="200" Height="100" />
<TextBlock go:SpotPanel.Spot="0 0"
go:SpotPanel.Alignment="1.0 1.0" Text="1 1" />
<TextBlock go:SpotPanel.Spot="0 0"
go:SpotPanel.Alignment="0.5 0.5" Text="0.5 0.5" />
<TextBlock go:SpotPanel.Spot="0 0"
go:SpotPanel.Alignment="0.0 0.0" Text="0 0" />
</go:SpotPanel>

Finally, Spots can have absolute offsets in addition to the fractional relative position. These offsets may be negative. You can specify the X and Y offsets as the third and fourth numbers. In this example there are three TextBlocks at the bottom-left corner. All have the default center alignment. One has an X offset of negative 30 (i.e. further towards the left), one is centered exactly at the bottom-left corner of the rectangle, and one is shifted towards the right by 30. Similarly there are three TextBlocks at the bottom-right corner, with one shifted up 10, and with one shifted down 10.
<go:SpotPanel>
<Rectangle go:SpotPanel.Main="True" Fill="LightCoral"
Width="200" Height="100" />
<TextBlock go:SpotPanel.Spot="0 1 -30 0" Text="-30 0" />
<TextBlock go:SpotPanel.Spot="0 1 0 0" Text="0 0" />
<TextBlock go:SpotPanel.Spot="0 1 30 0" Text="30 0" />
<TextBlock go:SpotPanel.Spot="1 1 0 -10" Text="0 -10" />
<TextBlock go:SpotPanel.Spot="1 1 0 0" Text="0 0" />
<TextBlock go:SpotPanel.Spot="1 1 0 10" Text="0 10" />
</go:SpotPanel>

The previous examples did not treat groups as nodes in their own right. As with regular Nodes, a link to a Group will by default treat the whole node as the only “port”. For example, connecting “Alpha” to “Epsilon” (instead of to “Gamma”) and “Epsilon” (instead of “Delta”) to “Beta” might result in the following screenshot. The “Alpha” and “Beta” nodes have been moved to make clearer the connections to the group.

The following example gives group nodes three ports on the left and two on the right, spaced equally within the thick border. The input ports on the left are named “zero”, “one”, and “two”; the output ports on the right are named “OutA” and “OutB”. This example has no text labels to visually name each port.
<DataTemplate x:Key="GroupTemplate3">
<Border x:Name="myBorder" CornerRadius="5"
BorderBrush="LightGreen" BorderThickness="10"
go:Node.LocationElementName="myGroupPanel">
<go:GroupPanel x:Name="myGroupPanel" Padding="10 5 10 5" Margin="0 20 0 0" >
<TextBlock x:Name="Label" go:Node.PortId=""
Text="{Binding Path=Data.Key}" FontSize="14" Foreground="Navy"
go:SpotPanel.Spot="1 0 0 -2" go:SpotPanel.Alignment="1 1" />
<Rectangle go:SpotPanel.Spot="0 0.25" go:SpotPanel.Alignment="1 0.5"
Fill="Blue" Width="10" Height="10" go:Node.PortId="zero" />
<Rectangle go:SpotPanel.Spot="0 0.50" go:SpotPanel.Alignment="1 0.5"
Fill="Blue" Width="10" Height="10" go:Node.PortId="one" />
<Rectangle go:SpotPanel.Spot="0 0.75" go:SpotPanel.Alignment="1 0.5"
Fill="Blue" Width="10" Height="10" go:Node.PortId="two" />
<Rectangle go:SpotPanel.Spot="1 0.33" go:SpotPanel.Alignment="0 0.5"
Fill="Orange" Width="10" Height="10" go:Node.PortId="OutA" />
<Rectangle go:SpotPanel.Spot="1 0.67" go:SpotPanel.Alignment="0 0.5"
Fill="Orange" Width="10" Height="10" go:Node.PortId="OutB" />
</go:GroupPanel>
</Border>
</DataTemplate>

This diagram was created with the same node data as before but with the following link data:
model.LinksSource = new ObservableCollection<MyLinkData>() {
new MyLinkData() { From="Alpha", To="Epsilon", ToPort="two" },
new MyLinkData() { From="Gamma", To="Delta" },
new MyLinkData() { From="Epsilon", To="Beta", FromPort="OutA" },
};
The above examples all intend to have each group exactly surround its collection of member nodes plus some padding. However, there are other scenarios where you want to treat each group as a fixed size box where the user might add or remove items (i.e. nodes) via drag-and-drop.
<DataTemplate x:Key="GroupTemplateFixedSize">
<StackPanel go:Node.LocationElementName="main"
go:Part.SelectionElementName="main"
go:Part.SelectionAdorned="True"
go:Part.DropOntoBehavior="AddsToGroup">
<TextBlock Text="{Binding Path=Data.Key}" FontWeight="Bold"
HorizontalAlignment="Left" />
<Rectangle x:Name="main" Fill="White" StrokeThickness="3"
Stroke="{Binding Path=Part.IsDropOntoAccepted,
Converter={StaticResource theStrokeChooser}}"
Width="100" Height="100" />
</StackPanel>
</DataTemplate>
Note the addition of go:Part.DropOntoBehavior="AddsToGroup". You can enable “drop onto” behavior by adding this attached property on groups and by also setting DraggingTool.DropOntoEnabled to true:
<go:Diagram Grid.Row="0" . . . >
<go:Diagram.DraggingTool>
<go:DraggingTool DropOntoEnabled="True" />
</go:Diagram.DraggingTool>
</go:Diagram>
This will allow users to drag nodes into and out of this rectangular box. When the drop occurs, the nodes become members of the group. That means that copying the group will also copy the members, and that deleting the group will also delete the members. Dragging a node out of such a group also removes it from that group – copying or deleting the group will have no effect on the dragged node.
To help provide feedback to the user, note the binding of the Rectangle.Stroke on the Part.IsDropOntoAccepted property. The DraggingTool will temporarily set that Part property during the dragging process if the dragged nodes might be added to that Group. You can override the DraggingTool.IsValidMember predicate to return false if you do not want a particular node to become a member of a particular group. For example, in the Planogram sample, IsValidMember is defined to return false when the dragged node is a Rack or a Shelf, to prevent nesting of Racks or Shelves.
The Planogram sample also demonstrates how these groups can be resizable by the user. Because the template is not using a GroupPanel, there are no inherent limits on where the group appears to be relative to its member nodes.
However, there may be times when you want to use a GroupPanel most of the time, but you still want to support drag-and-drop re-parenting of nodes between groups. The problem with the use of a GroupPanel is that as the user tries to drag a member node out of a group, the group automatically expands to include its member node. In this particular case you can use a GroupPanel when you also set its SurroundsMembersAfterDrop property to true. Basically the auto-sizing behavior of a GroupPanel is temporarily disabled during a move conducted by the DraggingTool.
<DataTemplate x:Key="GroupTemplateAddableRemovable">
<StackPanel go:Node.LocationElementName="main"
go:Part.SelectionElementName="main"
go:Part.SelectionAdorned="True"
go:Part.DropOntoBehavior="AddsToGroup">
<TextBlock Text="{Binding Path=Data.Key}" FontWeight="Bold"
HorizontalAlignment="Left" />
<Border Background="White" BorderThickness="3" CornerRadius="5"
BorderBrush="{Binding Path=Part.IsDropOntoAccepted,
Converter={StaticResource theStrokeChooser}}">
<go:GroupPanel x:Name="main" SurroundsMembersAfterDrop="True"
MinWidth="100" MinHeight="100" />
</Border>
</StackPanel>
</DataTemplate>
The positioning of FrameworkElements in Nodes is achieved with the standard WPF/Silverlight layout system, primarily the use of various kinds of Panels.
In GoXam diagrams, you can position a node by setting in XAML the Node.Location attached property on its root visual element, or by setting programmatically the Node.Location property. And users can reposition a node by dragging it.
However, there are also some automated means of positioning the nodes. These are implemented by several DiagramLayout classes, primarily: GridLayout, TreeLayout, ForceDirectedLayout, and LayeredDigraphLayout. Any layout can work with any kind of model.
A layout can be associated with a whole diagram by setting the Diagram.Layout property.
<go:Diagram . . .>
<go:Diagram.Layout>
<go:TreeLayout . . . />
</go:Diagram.Layout>
</go:Diagram>
A layout can also be associated with a Group by setting the Group.Layout attached property. If a Group has a layout, that layout will only position the members (nodes and links) of the group, and the Diagram’s layout will not operate on those members.
Because there may be many layouts present in a diagram, the Diagram.LayoutManager is responsible for managing them, including deciding when they need to run again. By default there are a number of events that may cause a re-layout. These cases are specified by the LayoutChange enumeration, such as LayoutChange .NodeAdded or LayoutChange .LinkRemoved.
Each DiagramLayout has a Conditions property that governs which LayoutChanges will cause a re-layout. The default behavior is to perform another layout when any node, link, or group membership is added or removed, or when a Layout is replaced or when a template is replaced. If you don’t want a layout to happen when users delete nodes or links, you could say:
<!-- WPF: -->
<go:TreeLayout Conditions="NodeAdded LinkAdded" . . . />
<!-- Silverlight -->
<golayout:TreeLayout ConditionFlags="NodeAdded LinkAdded" . . . />