Mindscape Mindblog

WPF Diagrams: Styling a Node Element

tag icon Tagged as WPF Diagramming

One of the most powerful and useful features of WPF Diagrams is the ability to style any part of a diagram any way you want it to look and feel.  One of the most common parts of a diagram that you’ll likely want to style are the node elements. When it comes to customizing the look of a node element there are 2 main things to think of first. First there is the style to be applied to the nodes, and secondly the data template that each node type should have. The data template is what seperates the look of one type of node to some other type of node. The style of all the nodes is generally the same, this is what provides a node with its MoveThumb, Resizer, and ConnectionPointThumbs. In this post we look at how to provide your own node style in your applications.

The standard DiagramNodeElement style uses a simple black colored Resizer, and blue gradient filled connection points as seen in below.

Standard WPF Diagram Node Style

The best place to start is by copying the standard DiagramNodeElement style. Below I have provided the style used in the WPF Flow Diagrams product.

<Style x:Key="NodeStyle" TargetType="{x:Type ms:DiagramNodeElement}">
    <Setter Property="FocusVisualStyle" Value="{x:Null}" />
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="ms:DiagramNodeElement">
          <Grid Canvas.Left="{TemplateBinding Left}" Canvas.Top="{TemplateBinding Top}">
              <ms:MoveThumb Element="{Binding RelativeSource={RelativeSource TemplatedParent}}" 
                             Focusable="True" Clip="{TemplateBinding Geometry}"
                             Style="{StaticResource {x:Static ms:MoveThumb.InvisibleStyleKey}}" Cursor="SizeAll" Name="Mover" />
            <ms:Resizer Element="{Binding RelativeSource={RelativeSource TemplatedParent}}" Name="Resizer" />
            <ContentPresenter Name="ContentPresenter" />
            <ms:ConnectionPointThumb ConnectionPoint="{Binding Content.DefaultConnectionPoint, RelativeSource={RelativeSource TemplatedParent}}"
                                        Width="{Binding ActualWidth, RelativeSource={RelativeSource TemplatedParent}}"
                                        Height="{Binding ActualHeight, RelativeSource={RelativeSource TemplatedParent}}"
                                        IsHitTestVisible="False" Focusable="True" Visibility="Collapsed"
                                        Name="Connector" Style="{StaticResource {x:Static ms:MoveThumb.InvisibleStyleKey}}" />
            <ItemsControl ItemsSource="{Binding Content.ConnectionPoints, RelativeSource={RelativeSource TemplatedParent}}" 
                          ItemTemplate="{StaticResource ConnectionPointTemplate}"
                          Name="Connectors"
                          Visibility="{TemplateBinding ShowConnectionPoints, Converter={StaticResource bvc}}">
              <ItemsControl.RenderTransform>
                <TranslateTransform X="{Binding (Canvas.Left), RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource Negative}}"
                                    Y="{Binding (Canvas.Top), RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource Negative}}" />
              </ItemsControl.RenderTransform>
              <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                  <Canvas />
                </ItemsPanelTemplate>
              </ItemsControl.ItemsPanel>
              <ItemsControl.ItemContainerStyle>
                <Style>
                  <Setter Property="Canvas.Left">
                    <Setter.Value>
                      <MultiBinding Converter="{StaticResource CPX}">
                        <Binding />
                        <Binding Path="Formatter.Layout" RelativeSource="{RelativeSource AncestorType={x:Type ms:DiagramSurface}}" />
                        <Binding Path="Position" />
                      </MultiBinding>
                    </Setter.Value>
                  </Setter>
                  <Setter Property="Canvas.Top">
                    <Setter.Value>
                      <MultiBinding Converter="{StaticResource CPY}">
                        <Binding />
                        <Binding Path="Formatter.Layout" RelativeSource="{RelativeSource AncestorType={x:Type ms:DiagramSurface}}" />
                        <Binding Path="Position" />
                      </MultiBinding>
                    </Setter.Value>
                  </Setter>
                </Style>
              </ItemsControl.ItemContainerStyle>
            </ItemsControl>
          </Grid>
          <ControlTemplate.Triggers>
            <Trigger Property="IsSelected" Value="False">
              <Setter TargetName="Resizer" Property="Visibility" Value="Collapsed" />
            </Trigger>
            <Trigger Property="IsResizable" Value="False">
              <Setter TargetName="Resizer" Property="Visibility" Value="Collapsed" />
            </Trigger>
            <DataTrigger Binding="{Binding IsReadOnly, RelativeSource={RelativeSource AncestorType={x:Type ms:DiagramSurface}}}" Value="True">
              <Setter TargetName="Resizer" Property="Visibility" Value="Collapsed" />
              <Setter TargetName="Mover" Property="Visibility" Value="Collapsed" />
              <Setter TargetName="Connector" Property="Visibility" Value="Collapsed" />
              <Setter TargetName="Connectors" Property="Visibility" Value="Collapsed" />
            </DataTrigger>
            <DataTrigger Binding="{Binding CanModifyConnectivity, RelativeSource={RelativeSource AncestorType={x:Type ms:DiagramSurface}}}" Value="False">
              <Setter TargetName="Connector" Property="Visibility" Value="Collapsed" />
              <Setter TargetName="Connectors" Property="Visibility" Value="Collapsed" />
            </DataTrigger>
            <MultiDataTrigger>
              <MultiDataTrigger.Conditions>
                <Condition Binding="{Binding Path=(ms:CursorAction.Current), Converter={StaticResource CursorActionTypeConverter}, RelativeSource={RelativeSource AncestorType={x:Type ms:DiagramSurface}}}" 
                           Value="{x:Static ms:CursorActionType.CreateConnection}" />
                <Condition Binding="{Binding IsReadOnly, RelativeSource={RelativeSource AncestorType={x:Type ms:DiagramSurface}}}"
                           Value="False" />
              </MultiDataTrigger.Conditions>
              <Setter TargetName="Mover" Property="IsHitTestVisible" Value="False" />
              <Setter TargetName="Connector" Property="IsHitTestVisible" Value="True" />
              <Setter TargetName="Connector" Property="Visibility" Value="Visible" />
            </MultiDataTrigger>
          </ControlTemplate.Triggers>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>

Â
Phew! That’s a lot of XAML! Good news though – you only need to tweak parts of it to start styling your own nodes.

After modifying the parts of the style that you need to customize, you then need to tell the DiagramSurface that this is the style you want to use. This is easily done by setting the NodeStyleSelector property of the IDiagramFormatter that you give to the DiagramSurface. If you wanted to use your style on all the different types of nodes in a diagram, then you would use the following code.

<ms:FixedStyleSelector x:Key="{x:Static styles:GradientStyle.NodeStyleSelectorKey}" Style="{StaticResource NodeStyle}" />

So why make your own node element style?

The node element style is the best way to incorporate your own SizeThumb and ConnectionPointThumb styles on your nodes. This is done simply by making your own style for these thumbs, and then setting them on the appropriate controls found within the node element style. This allows you to achieve the effects in the following images.

Changed WPF Diagrams node size thumb style

Changing the color of the Resizer, and only including the corner thumbs.

Changed WPF Diagram connection thumb style

Changing the look of the outbound connection point thumbs to have a transparent green fill.

Another useful tip with creating your own node styles is to add MouseDoubleClick or MouseRightButtonDown event handlers to the MoveThumb within the style. This will allow you to define appropriate actions to occur when the user double clicks or right click on a node. And since the MoveThumb in the standard node style is the most exposed control, it is the best place to attach such event handlers.

Node element styles are not limited to being applied to all the nodes in a single diagram. By using a TypeStyleSelector instead of a FixedStyleSelector, you can have some types nodes with different colored connection points compared to other nodes. Or some nodes could have the standard Resizer, while other nodes use a Resizer that only allows nodes to be square shaped.

Need any help with making your own node element styles? Drop a comment on this blog or put a post up on the forum. We’d love to hear from you!

Want to play with WPF Diagrams? Download the free trials of WPF Flow Diagrams or WPF Star Diagrams.

Leave a Reply