Use StyleSelectors in an MVVM Diagramming Application

This article describes how to use StyleSelectors in an MVVM application to apply different styles on the RadDiagram shapes and connections based on business logic.

For the purpose of this tutorial, we will populate the RadDiagram control with three different node types and a custom link implementation.

Please note that the examples in this tutorial are showcasing Telerik Windows8 theme. In the Implicit Styles article you can find more information on how to set an application-wide theme.

Let's start by creating RectangleNode, EllipseNode and DecisionNode classes to describe the RadDiagramShapes:

using Telerik.Windows.Controls.Diagrams.Extensions; 
 
public class EllipseNode : NodeViewModelBase 
{ 
    public EllipseNodeType Type { get; set; } 
} 
 
public enum EllipseNodeType 
{ 
    Start, 
    End 
} 
 
public class RectangleNode : NodeViewModelBase 
{ 
    public string Description { get; set; } 
} 
 
public class DecisionNode : NodeViewModelBase 
{ 
    public string Content { get; set; } 
} 
Imports Telerik.Windows.Controls.Diagrams.Extensions.ViewModels 
 
Public Class EllipseNode 
    Inherits NodeViewModelBase 
    Public Property Type() As EllipseNodeType 
End Class 
 
Public Enum EllipseNodeType 
    Start 
    [End] 
End Enum 
 
Public Class RectangleNode 
    Inherits NodeViewModelBase 
    Public Property Description() As String 
End Class 
 
Public Class DecisionNode 
    Inherits NodeViewModelBase 
    Public Overloads Property Content() As String 
End Class 

Please note that all three classes derive from the NodeViewModelBase class. You can find the implementation of this class in the Telerik.Windows.Controls.Diagrams.Extensions.ViewModels namespace as it is one of the ViewModels provided out-of-the-box in the Telerik.Windows.Controls.Diagrams.Extensions assembly.

Next, we need to define the ViewModel that will represent the RadDiagramConnections in our diagramming solution. For simplicity, we will create only one class but we will define a Type property to describe each link.

using Telerik.Windows.Controls.Diagrams.Extensions.ViewModels; 
 
public class Link : LinkViewModelBase<NodeViewModelBase> 
{ 
    public LinkType Type { get; set; } 
 
    public Link() 
        : base() 
    { } 
 
    public Link(NodeViewModelBase source, NodeViewModelBase target) 
        : base(source, target) 
    { 
    } 
} 
 
public enum LinkType 
{ 
    RightToLeft, 
    LeftToRight, 
    Normal 
} 
Imports Telerik.Windows.Controls.Diagrams.Extensions.ViewModels 
 
Public Class Link 
    Inherits LinkViewModelBase(Of NodeViewModelBase) 
    Public Property Type() As LinkType 
 
    Public Sub New() 
        MyBase.New() 
    End Sub 
 
    Public Sub New(ByVal source As NodeViewModelBase, ByVal target As NodeViewModelBase) 
        MyBase.New(source, target) 
    End Sub 
End Class 
 
Public Enum LinkType 
    RightToLeft 
    LeftToRight 
    Normal 
End Enum 

Please note that the Link class also derives from one of the ViewModels provided by the Diagramming Framework. The LinkViewModelBase class implementation can be found in the Telerik.Windows.Controls.Diagrams.Extensions.ViewModels namespace in the Telerik.Windows.Controls.Diagrams.Extensions assembly.

Now that our items' ViewModels are defined, we have to create a collection of items to pass to the RadDiagram.GraphSource. Telerik Diagramming Framework provides a few built-in ViewModels that can be used out-of-the-box when creating a collection of diagramming items. You can examine the following list of tutorials to get a better understanding of the different scenarios and collections you can use in an MVVM diagramming solution:

In this example we will use the ObservableGraphSourceBase class to create a custom GraphSource implementation as this will allow us to take advantage of the predefined AddNode(), AddLink(), CreateNode(), CreateLink(), RemoveNode() and RemoveLink() methods. This way we won't have to explicitly define a custom add/create/remove logic for our business items.

public class GraphSource : ObservableGraphSourceBase<NodeViewModelBase, Link> 
{ 
}    
Public Class GraphSource 
    Inherits ObservableGraphSourceBase(Of NodeViewModelBase, Link) 
End Class      

Let's define the items in the GraphSource to describe the following process workflow: Rad Diagram How To Style Selectors Goal

public class GraphSource : ObservableGraphSourceBase<NodeViewModelBase, Link> 
{ 
    public void PopulateGraphSource() 
    { 
        //Add Nodes 
        RectangleNode processNode1 = new RectangleNode() 
        { 
            Position = new Point(160, 280), 
            Description = "Process 1" 
        }; 
        this.AddNode(processNode1); 
        RectangleNode processNode2 = new RectangleNode() 
        { 
            Position = new Point(644, 280), 
            Description = "Process 2.1" 
        }; 
        this.AddNode(processNode2); 
        RectangleNode processNode3 = new RectangleNode() 
        { 
            Position = new Point(644, 420), 
            Description = "Process 2.2" 
        }; 
        this.AddNode(processNode3); 
        RectangleNode processNode4 = new RectangleNode() 
        { 
            Position = new Point(644, 140), 
            Description = "Process 2.3" 
        }; 
        this.AddNode(processNode4); 
        RectangleNode processNode5 = new RectangleNode() 
        { 
            Position = new Point(880, 280), 
            Description = "Process 3" 
        }; 
        this.AddNode(processNode5); 
        RectangleNode processNode6 = new RectangleNode() 
        { 
            Position = new Point(420, 420), 
            Description = "Process A" 
        }; 
        this.AddNode(processNode6); 
        RectangleNode processNode7 = new RectangleNode() 
        { 
            Position = new Point(160, 420), 
            Description = "Process B" 
        }; 
        this.AddNode(processNode7); 
 
        DecisionNode decisionNode = new DecisionNode() 
        { 
            Position = new Point(420, 280), 
            Content = "condition" 
        }; 
        this.AddNode(decisionNode); 
        EllipseNode endNode = new EllipseNode() 
        { 
            Position = new Point(1100, 300), 
            Type = EllipseNodeType.End 
        }; 
        this.AddNode(endNode); 
        EllipseNode startNode = new EllipseNode() 
       { 
           Position = new Point(60, 300), 
           Type = EllipseNodeType.Start, 
           Content = "Start" 
       }; 
        this.AddNode(startNode); 
 
        //Add Links 
        this.AddLink(new Link(startNode, processNode1) { Type = LinkType.LeftToRight }); 
        this.AddLink(new Link(processNode1, decisionNode) { Type = LinkType.LeftToRight }); 
        this.AddLink(new Link(decisionNode, processNode2) { Type = LinkType.LeftToRight }); 
        this.AddLink(new Link(processNode2, processNode3) { Type = LinkType.Normal }); 
        this.AddLink(new Link(processNode2, processNode4) { Type = LinkType.Normal }); 
        this.AddLink(new Link(processNode2, processNode5) { Type = LinkType.LeftToRight }); 
        this.AddLink(new Link(processNode3, processNode5) { Type = LinkType.LeftToRight }); 
        this.AddLink(new Link(processNode4, processNode5) { Type = LinkType.LeftToRight }); 
        this.AddLink(new Link(processNode5, endNode) { Type = LinkType.LeftToRight }); 
        this.AddLink(new Link(processNode6, decisionNode) { Type = LinkType.RightToLeft }); 
        this.AddLink(new Link(processNode7, processNode6) { Type = LinkType.RightToLeft }); 
        this.AddLink(new Link(processNode1, processNode7) { Type = LinkType.RightToLeft }); 
    } 
} 
Public Class GraphSource 
    Inherits ObservableGraphSourceBase(Of NodeViewModelBase, Link) 
    Public Sub PopulateGraphSource() 
        'Add Nodes' 
        Dim processNode1 As New RectangleNode() With {.Position = New Point(160, 280), .Description = "Process 1"} 
        Me.AddNode(processNode1) 
        Dim processNode2 As New RectangleNode() With {.Position = New Point(644, 280), .Description = "Process 2.1"} 
        Me.AddNode(processNode2) 
        Dim processNode3 As New RectangleNode() With {.Position = New Point(644, 420), .Description = "Process 2.2"} 
        Me.AddNode(processNode3) 
        Dim processNode4 As New RectangleNode() With {.Position = New Point(644, 140), .Description = "Process 2.3"} 
        Me.AddNode(processNode4) 
        Dim processNode5 As New RectangleNode() With {.Position = New Point(880, 280), .Description = "Process 3"} 
        Me.AddNode(processNode5) 
        Dim processNode6 As New RectangleNode() With {.Position = New Point(420, 420), .Description = "Process A"} 
        Me.AddNode(processNode6) 
        Dim processNode7 As New RectangleNode() With {.Position = New Point(160, 420), .Description = "Process B"} 
        Me.AddNode(processNode7) 
 
        Dim decisionNode_Renamed As New DecisionNode() With {.Position = New Point(420, 280), .Content = "condition"} 
        Me.AddNode(decisionNode_Renamed) 
        Dim endNode As New EllipseNode() With {.Position = New Point(1100, 300), .Type = EllipseNodeType.End} 
        Me.AddNode(endNode) 
        Dim startNode As New EllipseNode() With {.Position = New Point(60, 300), .Type = EllipseNodeType.Start, .Content = "Start"} 
        Me.AddNode(startNode) 
 
        'Add Links' 
        Me.AddLink(New Link(startNode, processNode1) With {.Type = LinkType.LeftToRight}) 
        Me.AddLink(New Link(processNode1, decisionNode_Renamed) With {.Type = LinkType.LeftToRight}) 
        Me.AddLink(New Link(decisionNode_Renamed, processNode2) With {.Type = LinkType.LeftToRight}) 
        Me.AddLink(New Link(processNode2, processNode3) With {.Type = LinkType.Normal}) 
        Me.AddLink(New Link(processNode2, processNode4) With {.Type = LinkType.Normal}) 
        Me.AddLink(New Link(processNode2, processNode5) With {.Type = LinkType.LeftToRight}) 
        Me.AddLink(New Link(processNode3, processNode5) With {.Type = LinkType.LeftToRight}) 
        Me.AddLink(New Link(processNode4, processNode5) With {.Type = LinkType.LeftToRight}) 
        Me.AddLink(New Link(processNode5, endNode) With {.Type = LinkType.LeftToRight}) 
        Me.AddLink(New Link(processNode6, decisionNode_Renamed) With {.Type = LinkType.RightToLeft}) 
        Me.AddLink(New Link(processNode7, processNode6) With {.Type = LinkType.RightToLeft}) 
        Me.AddLink(New Link(processNode1, processNode7) With {.Type = LinkType.RightToLeft}) 
 
    End Sub 
End Class 

Now we can set-up our RadDiagram control to display these items. For that purpose we can define a RadDiagram instance in our view and set its GraphSource property in the code-behind file:

<Grid> 
    <telerik:RadDiagram x:Name="xDiagram" /> 
</Grid> 

public MainView() 
{ 
    InitializeComponent(); 
 
    GraphSource DiagramSource = new GraphSource(); 
    DiagramSource.PopulateGraphSource(); 
    this.xDiagram.GraphSource = DiagramSource; 
} 
Partial Public Class MainWindow 
    Inherits Window 
    Public Sub New() 
        InitializeComponent() 
 
        Dim DiagramSource As New GraphSource() 
        DiagramSource.PopulateGraphSource() 
        Me.xDiagram.GraphSource = DiagramSource 
    End Sub 
End Class 

This operation will place all DiagramItems at a position of (0,0) on top of each other. This is why we will need to apply a custom style to bind the Position of the items to the business values we defined in the GraphSource collection. However, as our process workflow uses different types of shapes, we will have to apply different styles for each node type. This is where we can take advantage of the RadDiagram.ShapeStyleSelector property and create a custom StyleSelector for our nodes.

As our example defines three different business nodes, we can create a StyleSelector that applies a style based on the type of the business class. Moreover, as the EllipseNode class exposes a Type property, we can also use its value to apply different styles for the start and end points of our process workflow.

public class NodeStyleSelector : StyleSelector 
{ 
    public Style DecisionNodeStyle { get; set; } 
    public Style StartNodeStyle { get; set; } 
    public Style EndNodeStyle { get; set; } 
    public Style RectangleNodeStyle { get; set; } 
 
    public override Style SelectStyle(object item, DependencyObject container) 
    { 
        if (item is DecisionNode) 
            return DecisionNodeStyle; 
        else if (item is RectangleNode) 
            return RectangleNodeStyle; 
        else if (item is EllipseNode) 
        { 
            switch (((EllipseNode)item).Type) 
            { 
                case EllipseNodeType.Start: 
                    return StartNodeStyle; 
                case EllipseNodeType.End: 
                    return EndNodeStyle; 
                default: 
                    return base.SelectStyle(item, container); 
            } 
        } 
        else return base.SelectStyle(item, container); 
    } 
} 
Public Class NodeStyleSelector 
    Inherits StyleSelector 
    Public Property DecisionNodeStyle() As Style 
    Public Property StartNodeStyle() As Style 
    Public Property EndNodeStyle() As Style 
    Public Property RectangleNodeStyle() As Style 
 
    Public Overrides Function SelectStyle(ByVal item As Object, ByVal container As DependencyObject) As Style 
        If TypeOf item Is DecisionNode Then 
            Return DecisionNodeStyle 
        ElseIf TypeOf item Is RectangleNode Then 
            Return RectangleNodeStyle 
        ElseIf TypeOf item Is EllipseNode Then 
            Select Case (CType(item, EllipseNode)).Type 
                Case EllipseNodeType.Start 
                    Return StartNodeStyle 
                Case EllipseNodeType.End 
                    Return EndNodeStyle 
                Case Else 
                    Return MyBase.SelectStyle(item, container) 
            End Select 
        Else 
            Return MyBase.SelectStyle(item, container) 
        End If 
    End Function 
End Class 

Now, let's declare this selector in the resources of our view and prepare the custom styles for each node type:

    <Grid> 
        <Grid.Resources> 
            <Style x:Key="DecisionShapeStyle" TargetType="telerik:RadDiagramShape"> 
                <Setter Property="Position" Value="{Binding Position}" /> 
                <Setter Property="Height" Value="80" /> 
                <Setter Property="Geometry" Value="{telerik:FlowChartShape ShapeType=DecisionShape}" /> 
                <Setter Property="ContentTemplate"> 
                    <Setter.Value> 
                        <DataTemplate> 
                            <TextBlock Text="{Binding Content}" /> 
                        </DataTemplate> 
                    </Setter.Value> 
                </Setter> 
            </Style> 
            <Style x:Key="RectangleShapeStyle" TargetType="telerik:RadDiagramShape"> 
                <Setter Property="Position" Value="{Binding Position}" /> 
                <Setter Property="Height" Value="80" /> 
                <Setter Property="Geometry" Value="{telerik:CommonShape ShapeType=RectangleShape}" /> 
                <Setter Property="ContentTemplate"> 
                    <Setter.Value> 
                        <DataTemplate> 
                            <TextBlock Text="{Binding Description}" /> 
                        </DataTemplate> 
                    </Setter.Value> 
                </Setter> 
            </Style> 
            <Style x:Key="StartShapeStyle" TargetType="telerik:RadDiagramShape"> 
                <Setter Property="Position" Value="{Binding Position}" /> 
                <Setter Property="Width" Value="40" /> 
                <Setter Property="Height" Value="40" /> 
                <Setter Property="Geometry" Value="{telerik:CommonShape ShapeType=EllipseShape}" /> 
                <Setter Property="ContentTemplate"> 
                    <Setter.Value> 
                        <DataTemplate> 
                            <Grid /> 
                        </DataTemplate> 
                    </Setter.Value> 
                </Setter> 
            </Style> 
            <Style x:Key="EndShapeStyle" TargetType="telerik:RadDiagramShape"> 
                <Setter Property="Position" Value="{Binding Position}" /> 
                <Setter Property="Width" Value="40" /> 
                <Setter Property="Height" Value="40" /> 
                <Setter Property="Geometry" Value="{telerik:CommonShape ShapeType=EllipseShape}" /> 
                <Setter Property="ContentTemplate"> 
                    <Setter.Value> 
                        <DataTemplate> 
                            <Ellipse Width="20" Height="20" Fill="#FF333333" /> 
                        </DataTemplate> 
                    </Setter.Value> 
                </Setter> 
            </Style> 
            <styleselectors:NodeStyleSelector x:Key="CustomShapeStyleSelector" 
                    DecisionNodeStyle="{StaticResource DecisionShapeStyle}" 
                    EndNodeStyle="{StaticResource EndShapeStyle}" 
                    RectangleNodeStyle="{StaticResource RectangleShapeStyle}" 
                    StartNodeStyle="{StaticResource StartShapeStyle}" /> 
        </Grid.Resources> 
        <telerik:RadDiagram x:Name="xDiagram" 
                ShapeStyleSelector="{StaticResource CustomShapeStyleSelector}"/> 
    </Grid> 

If we run the solution at this point, we should get the following result:Rad Diagram How To Style Selectors Shapes

Now the RadDiagramShapes are properly styled and arranged, but the connections don't look all that good. This is due to the fact that they try to display their content and as we haven't declared a ConnectionTemplate, the Link class ToSting() method is used to display it. In this example we won't need to visualize any labels or descriptions near the connections, so we can define an empty Grid as a ContentTemplate of each RadDiagramConnection. Also, as we created a Type property in the Link class implementation and we added different types of links in the GraphSource collection, we can apply different styles for the connections based on their type. For that purpose, we will need another custom StyleSelector - this time we will use it with the RadDiagram.ConnectionStyleSelector property.

public class LinkStyleSelector : StyleSelector 
{ 
    public Style NormalLinkStyle { get; set; } 
    public Style RightCapLinkStyle { get; set; } 
    public Style LeftCapLinkStyle { get; set; } 
 
    public override Style SelectStyle(object item, DependencyObject container) 
    { 
        Link link = item as Link; 
        if (link == null) 
            return base.SelectStyle(item, container); 
        else switch (link.Type) 
            { 
                case LinkType.RightToLeft: 
                    return LeftCapLinkStyle; 
                case LinkType.LeftToRight: 
                    return RightCapLinkStyle; 
                case LinkType.Normal: 
                    return NormalLinkStyle; 
                default: 
                    return base.SelectStyle(item, container); 
            } 
    } 
} 
Public Class LinkStyleSelector 
    Inherits StyleSelector 
    Public Property NormalLinkStyle() As Style 
    Public Property RightCapLinkStyle() As Style 
    Public Property LeftCapLinkStyle() As Style 
 
    Public Overrides Function SelectStyle(ByVal item As Object, ByVal container As DependencyObject) As Style 
        Dim link_Renamed As Link = TryCast(item, Link) 
        If link_Renamed Is Nothing Then 
            Return MyBase.SelectStyle(item, container) 
        Else 
            Select Case link_Renamed.Type 
                Case LinkType.RightToLeft 
                    Return LeftCapLinkStyle 
                Case LinkType.LeftToRight 
                    Return RightCapLinkStyle 
                Case LinkType.Normal 
                    Return NormalLinkStyle 
                Case Else 
                    Return MyBase.SelectStyle(item, container) 
            End Select 
        End If 
    End Function 
End Class 

And finally we need to define the connection styles and the LinkStyleSelector in the resources of our view:

<Grid> 
    <Grid.Resources> 
        <Style TargetType="telerik:RadDiagramConnection" x:Key="NormalConnectionStyle"> 
            <Setter Property="Stroke" Value="Brown" /> 
            <Setter Property="StrokeThickness" Value="2" /> 
            <Setter Property="StrokeDashArray" Value="2 2" /> 
            <Setter Property="ContentTemplate"> 
                <Setter.Value> 
                    <DataTemplate> 
                        <Grid /> 
                    </DataTemplate> 
                </Setter.Value> 
            </Setter> 
        </Style> 
        <Style TargetType="telerik:RadDiagramConnection" x:Key="TargetCapConnectionStyle"> 
            <Setter Property="TargetCapType" Value="Arrow1Filled" /> 
            <Setter Property="ContentTemplate"> 
                <Setter.Value> 
                    <DataTemplate> 
                        <Grid /> 
                    </DataTemplate> 
                </Setter.Value> 
            </Setter> 
        </Style> 
        <Style TargetType="telerik:RadDiagramConnection" x:Key="SourceCapConnectionStyle"> 
            <Setter Property="Stroke" Value="Red" /> 
            <Setter Property="SourceCapType" Value="Arrow1Filled" /> 
            <Setter Property="ContentTemplate"> 
                <Setter.Value> 
                    <DataTemplate> 
                        <Grid /> 
                    </DataTemplate> 
                </Setter.Value> 
            </Setter> 
        </Style> 
        <styleselectors:LinkStyleSelector x:Key="CustomConnectionStyleSelector" 
                NormalLinkStyle="{StaticResource NormalConnectionStyle}" 
                RightCapLinkStyle="{StaticResource TargetCapConnectionStyle}" 
                LeftCapLinkStyle="{StaticResource SourceCapConnectionStyle}" /> 
     ... 
    </Grid.Resources> 
    <telerik:RadDiagram x:Name="xDiagram" 
            ConnectionStyleSelector="{StaticResource CustomConnectionStyleSelector}" 
            ShapeStyleSelector="{StaticResource CustomShapeStyleSelector}"/> 
</Grid> 

The final result of the solution we build should look like this:Rad Diagram How To Style Selectors Result

See Also

In this article