WPF Diagrams defines a number of standard shapes that cover many common diagramming needs. You can also create your own shapes which users can manipulate in the same way as the standard shapes.

Declaring a Custom Shape

The first step is to create an identifier for the custom shape. This is an object of type DiagramShape. The identifier just specifies some basic information about the shape, such as its name.
CopyCreating a shape identifier
public static class MyShapes
{
  public static readonly DiagramShape WShape = new DiagramShape("WShape").Freeze();
}

By default, the shape’s display name is the same as its name, and it has one connection point on each side. You can override these defaults using properties of DiagramShape:

CopyCreating a shape identifier with custom settings
public static readonly DiagramShape WShape = new DiagramShape("WShape")
{
  DisplayName = "W Shape",
  TopConnectionPointCount = 0,
  BottomConnectionPointCount = 3
}.Freeze();

The Freeze method prevents users of the shape from accidentally modifying these settings after you have specified them, so it’s a good idea to call Freeze on any shapes you declare.
 

Laying Out the Custom Shape

The identifier doesn’t specify the geometry of the custom shape, because it’s usually more convenient to describe geometry and layout in XAML rather than in code. To specify the layout of the shape, create a ShapeLayout resource in XAML, whose key is the shape identifier:

CopySpecifying the shape geometry
<ms:ShapeLayout x:Key="{x:Static local:MyShapes.WShape}" 
                Geometry="M 0 0 L 0 1 L 0.5 0.5 L 1 1 L 1 0 Z" 
                />

The geometry is relative to the bounding box, so the top left is 0 0 and the bottom right is 1 1. WPF Diagrams will scale the geometry to the actual size of the node as required.

By default, the connection point positions are halfway along each side of the shape’s bounding box. You can override this using ShapeLayout.ConnectionPointPositions. You can populate this with ShapeConnectionPointPosition objects using XAML collection syntax, or use a shorthand notation similar to the geometry notation:

CopySpecifying connection point positions using shorthand
<!-- Use L, T, R, B to indicate which side the positions are for
     Use semicolons to separate positions
     Specify each position as x,y where 0 is top/left and 1 is bottom/right of the bounding box
     Optionally apply absolute position modifiers using + and - e.g. 0 + 20 is 20px from top/left, 1 - 30 is 30px from bottom/right
     NOTE: Unlike geometry notation, commas in positions are NOT optional
-->
<ms:ShapeLayout ConnectionPointPositions="L 0,0.25 R 1,0.25 B 0,1; 0.5,0.5; 1,1" />

By default, the content of the node is displayed at the centre of the bounding box. If the shape layout is imbalanced, this may not be central within the shape. To control the position of the content, set the ShapeLayout.ContentMargins property. You can use star sizing to specify that the margin should be a proportion of the shape size. For example, in the W shape the content might be placed towards the top of the shape, so you would specify a large bottom margin compared to the top.

CopyOverriding the content margin
<ms:ShapeLayout ContentMargin="*,*,*,4*" />  <!-- LTRB order -->

Laying Out Compound Shapes

Some shapes cannot be described as a single geometry because they consist of multiple sections which may need different shading or styling. An example of this in the built-in shapes is the curved arrow, where the ‘back’ of the arrow appears shaded. To do this in your own shapes, specify the ShapeLayout.Paths collection instead of ShapeLayout.Geometry. You can then apply styles directly to the individual paths.

Adding the Custom Shape to the Toolbox

You can add the custom shape to the toolbox in the same way as built-in shapes, using the DiagramNodeTool control and the ShapeTool.Shape attached property. To identify the shape in ShapeTool.Shape, specify the shape identifier object:

CopyAdding a custom shape to the toolbox
<ms:DiagramNodeTool ms:ShapeTool.Shape="{x:Static local:MyShapes.WShape}" />

If your shape is a compound shape, defined using ShapeLayout.Paths instead of ShapeLayout.Geometry, you may find that the styles you have defined for paths do not work well in the reduced-size environment of the toolbox. For each path, you can specify a style to be used in the toolbox by setting the ShapeLayout.ToolboxIconStyle attached property on that path.

If a path represents fine detail that you don’t want to show in the toolbox at all, give it an all-transparent ToolboxIconStyle.
 

Similarly, you can control how paths are rendered when dragging the tool over the diagram surface using the ShapeLayout.CursorVisualStyle attached property.

Serializing and Deserializing Custom Shapes

The DiagramXmlSerializer serializes custom shape nodes using the name you specified in the shape identifier. You will therefore want to make sure that all the shapes you allow have unique names.

However, when deserializing, the DiagramXmlSerializer does not know how to resolve user-defined shape names. You must therefore set DiagramXmlSerializer.ShapeNameResolver to a function which maps custom shape names to custom shape identifier objects:

CopyC#
serializer.ShapeNameResolver = name => name == "WShape" ? MyShapes.WShape : null;

Your function should return null to indicate that it doesn’t recognise the shape name.