Custom Nodes in WPF Diagram

Here we will look how to define custom diagram nodes in the WPF diagram control, how to style them, how to make their properties appear in the property grid and how to save and load them with the diagram’s saveToXml and loadFromXml methods.

Here is a screenshot of our SubjectNode custom node class that is used in an application for a school curriculum:

I. XAML Template

You will need to add a XAML template for the node us you are creating a custom node because you want to have special-looking nodes. Let’s create a node that has 3 text fields and a background. We will declare the template for this node that we call SubjectNode in XAML this way:

<style targettype="local:SubjectNode">
    <Setter Property="Template">
      <Setter.Value>
        <DataTemplate DataType="local:SubjectNode">
          <Grid>

            <Rectangle
		Stroke="{Binding Stroke}"
		Fill="{Binding Background}" />

            <Grid>              

               <StackPanel Margin="4,8,0,0"  Orientation="Vertical" Grid.Column="1">
                <TextBlock Text="{Binding Subject}" FontWeight="800" Foreground="Black" />
                <TextBlock Text="{Binding Teacher}" Foreground="Blue" />
                <TextBlock Text="{Binding Remarks}" FontSize="9" Foreground="Black" />
              </StackPanel>
            </Grid>

          </Grid>
        </DataTemplate>
      </Setter.Value>
    </Setter>
  </style>

That goes in the contents of <ResourceDictionary>…..</ResourceDictionary> in the xaml file where you store this resourrce dictionary.

You see here that we use a gird as the principal layout container. There we add a rectangle, whose Fill property is bound to a property called Background in the SubjectNode. Next we have another grid that holds a StackPanel. The stack panel is with vertical orientation and it arranges the three TextBlock-s for the three custom fields of the node.

II. Declaring the Custom Node Class

When you create a custom node you need to inherit the TemplatedNode class. In the static construcotr you should call OverrideMetadata on the DefaultStyleKeyProperty to make it use the template that we’ve declared in XAML:

public class SubjectNode : TemplatedNode
{
	static SubjectNode()
	{
		DefaultStyleKeyProperty.OverrideMetadata(
			typeof(SubjectNode), new FrameworkPropertyMetadata(typeof(SubjectNode)));
}

public SubjectNode()
{			
}

Then we declare a constructor without any parameters that is required for the node to be created in XAML. If you want users to be able to create instance of the SubjectNode through drag and drop, you need to declare one more constructor:

	// Required for creating nodes by dragging them from the NodeListView
public SubjectNode(SubjectNode prototype) : base(prototype)
{
	Subject = prototype.Subject;
	Teacher = prototype.Teacher;
	Remarks = prototype.Remarks;
}

III. Properties

We declare the properties that we want: Subject, TeacherName, Remarks and Background as dependency properties the standard way:

public Brush Background
{
	get { return (Brush)GetValue(BackgroundProperty); }
	set { SetValue(BackgroundProperty, value); }
}

public static readonly DependencyProperty BackgroundProperty = DependencyProperty.Register(
	"Background",
	typeof(Brush),
	typeof(SubjectNode),
	new PropertyMetadata(new SolidColorBrush(Color.FromRgb(223, 235, 250))));

and for the text properties:

public string Remarks
{
	get { return (string)GetValue(RemarksProperty); }
	set { SetValue(RemarksProperty, value); }
}

public static readonly DependencyProperty RemarksProperty = DependencyProperty.Register(
	"Remarks",
	typeof(string),
	typeof(SubjectNode),
	new PropertyMetadata(""));

If we want the properties to be listed in a property grid we need to add a new class that inherits from DiagramNodeProperties. In it we do nothing but list the custom properties together with their type:

public class SubjectNodeProperties : DiagramNodeProperties
{
        internal string Subject;
	internal string Teacher;
	internal string Remarks;
	internal Brush Background;
} 

IV. More Options

Standard diagram nodes support undo and redo as well serialization out of the box. If you want your custom class to support those features as well you need to implement a few more methods. The methods to support undo/redo are SaveProperties and RestoreProperties. They take an instance of the DiagramItemProperties class that allows you to transfer data between the instance of the current node and its DiagramItemProperties instance that store the values of the node’s properties:

protected override void RestoreProperties(DiagramItemProperties props)
{
	base.RestoreProperties(props);
	var state = (SubjectNodeProperties)props;
	Subject = state.Subject;
	Teacher = state.Teacher;
	Remarks = state.Remarks;
	Background = state.Background;
}

protected override void SaveToXml(XmlElement xmlElement, XmlPersistContext context)
{
	base.SaveToXml(xmlElement, context);
	context.WriteString(Subject, "Subject", xmlElement);
	context.WriteString(Teacher, "Teacher", xmlElement);
	context.WriteString(Remarks, "Remarks", xmlElement);
	context.WriteBrush(Background, "Background", xmlElement);
}

The Diagram uses XML for serialization, so if you want your node to be saved and loaded correctly through the Diagram‘s saveToXml and loadFromXml methods you should implement SaveToXml and LoadFromXml. There you write the values o the custom properties of SubjectNode to XML elements and read them from XML elements as well:

protected override void SaveToXml(XmlElement xmlElement, XmlPersistContext context)
{
	base.SaveToXml(xmlElement, context);
	context.WriteString(Subject, "Subject", xmlElement);
	context.WriteString(Teacher, "Teacher", xmlElement);
	context.WriteString(Remarks, "Remarks", xmlElement);
	context.WriteBrush(Background, "Background", xmlElement);
}

protected override void LoadFromXml(XmlElement xmlElement, XmlPersistContext context)
{
	base.LoadFromXml(xmlElement, context);
	Subject = context.ReadString("Subject", xmlElement);
	Teacher = context.ReadString("Teacher", xmlElement);
	Remarks = context.ReadString("Remarks", xmlElement);
	Background = context.ReadBrush("Background", xmlElement);
}

You can download the sample that uses custom SubjectNode from https://mindfusion.eu/samples/wpf/diagram/Curriculum.zip

About Diagramming for WPF: This is the right tool to create flowcharts in WPF that always meet your requirements. The library offers more than 100 predefined node shapes, extensive event set and more than 15 exporters and importers. Each diagram that you build has a completely customizable look through styles, themes and appearance properties for each part of the flowchart. The numerous samples and detailed documentation help you learn quickly how to integrate the component into your own application. You can download the trial version, which has no feature restrictions and does not expire from the WPF Diagram Page on MindFusion website.