Serialize a Databound Diagram

This article shows how to serialize and deserialize the RadDiagram in MVVM scenarios. It also demonstrates how to save/load custom properties from the ViewModels during the serialization / deserialization process.

Please keep in mind that the Serialization / Deserialization is internally used in the copy / paste operations of the RadDiagramShapes and RadDiagramConnections.

ISerializableGraphSource and SerializableGraphSourceBase

When you need to serialize / deserialize a data-bound RadDiagram, you have to use a GraphSource which implements the ISerializableGraphSource interface from the Telerik.Windows.Diagrams.Core namespace.

public interface ISerializableGraphSource : IObservableGraphSource 
{ 
    void SerializeNode(object model, SerializationInfo info); 
    void SerializeLink(ILink link, SerializationInfo info); 
 
    object DeserializeNode(IShape shape, SerializationInfo info); 
    ILink DeserializeLink(IConnection connection, SerializationInfo info); 
} 

IObservableGraphSource enables the two-way binding capabilities of the RadDiagram. ISerializableGraphSource (from Telerik.Windows.Controls.Diagrams.Extensions.ViewModels namespace) extends it with Serialization and Deserialization methods for "Links" and "Nodes".

Similarly, the SerializableGraphSourceBase class extends the ObservableGraphSourceBase.

namespace Telerik.Windows.Controls.Diagrams.Extensions.ViewModels 
{ 
    public abstract class SerializableGraphSourceBase<TNode, TLink> : ObservableGraphSourceBase<TNode, TLink>, 
                                                                      ISerializableGraphSource where TLink : ILink 
    { 
        public abstract string GetNodeUniqueId(TNode node); 
    } 
} 

Please note that when you use the SerializableGraphSourceBase, you have to override the GetNodeUniqueId() method which must return a unique string identifier of your TNode ViewModel. Furthermore your TNode implementation must provide a parameterless constructor.

Serializing ViewModel's properties

The following section will show you how to save and load properties from your ViewModels during the serialization / deserialization process.

Let's first create a proper ViewModels. Do not forget to create parameterless constructors and unique ID properties:

public class OrgItem : HierarchicalNodeViewModel 
{ 
    public OrgItem() 
    { 
    } 
 
    public OrgItem(string title) 
    { 
        this.Title = title; 
    } 
 
    private string title; 
    public string Title 
    { 
        get { return this.title; } 
        set 
        { 
            if (this.title != value) 
            { 
                this.title = value; 
                this.OnPropertyChanged("Title"); 
            } 
        } 
    } 
 
    public string Id { get; set; } 
} 
 
public class OrgLink : LinkViewModelBase<OrgItem> 
{ 
    public OrgLink() 
    { 
    } 
 
    public OrgLink(OrgItem source, OrgItem target) 
        : base(source, target) 
    { 
    } 
 
    public string Id { get; set; } 
} 
Public Class OrgItem 
    Inherits HierarchicalNodeViewModel 
    Public Sub New() 
    End Sub 
 
    Public Sub New(title As String) 
        Me.Title = title 
    End Sub 
 
    Private m_title As String 
    Public Property Title() As String 
        Get 
            Return Me.m_title 
        End Get 
        Set(value As String) 
            If Me.m_title <> value Then 
                Me.m_title = value 
                Me.OnPropertyChanged("Title") 
            End If 
        End Set 
    End Property 
 
    Public Property Id() As String 
        Get 
            Return m_Id 
        End Get 
        Set(value As String) 
            m_Id = Value 
        End Set 
    End Property 
    Private m_Id As String 
End Class 
 
Public Class OrgLink 
    Inherits LinkViewModelBase(Of OrgItem) 
    Public Sub New() 
    End Sub 
 
    Public Sub New(source As OrgItem, target As OrgItem) 
        MyBase.New(source, target) 
    End Sub 
 
    Public Property Id() As String 
        Get 
            Return m_Id 
        End Get 
        Set(value As String) 
            m_Id = Value 
        End Set 
    End Property 
    Private m_Id As String 
End Class 

Then our GraphSource should look like this:

public class GraphSource : SerializableGraphSourceBase<OrgItem, OrgLink> 
{ 
    public override string GetNodeUniqueId(OrgItem node) 
    { 
        return node.Id; 
    } 
 
    public override void SerializeNode(OrgItem node, Telerik.Windows.Diagrams.Core.SerializationInfo info) 
    { 
        base.SerializeNode(node, info); 
        info["Title"] = node.Title; 
    } 
 
    public override OrgItem DeserializeNode(Telerik.Windows.Diagrams.Core.IShape shape, Telerik.Windows.Diagrams.Core.SerializationInfo info) 
    { 
        base.DeserializeNode(shape, info); 
        if (info["Title"] != null) 
        { 
            return new OrgItem(info["Title"].ToString()); 
        } 
        return null; 
    } 
} 
Public Class GraphSource 
    Inherits SerializableGraphSourceBase(Of OrgItem, OrgLink) 
 
    Public Overrides Function GetNodeUniqueId(node As OrgItem) As String 
        Return node.Id 
    End Function 
 
    Public Overrides Sub SerializeNode(node As OrgItem, info As Telerik.Windows.Diagrams.Core.SerializationInfo) 
        MyBase.SerializeNode(node, info) 
        info("Title") = node.Title 
    End Sub 
 
    Public Overrides Function DeserializeNode(shape As Telerik.Windows.Diagrams.Core.IShape, info As Telerik.Windows.Diagrams.Core.SerializationInfo) As OrgItem 
        MyBase.DeserializeNode(shape, info) 
        If info("Title") IsNot Nothing Then 
            Return New OrgItem(info("Title").ToString()) 
        End If 
        Return Nothing 
    End Function 
End Class 

If you do not override the GetNodeUniqueId() method, you will receive a compilation error. Also please note that you need to make sure that the method returns a unique value for every Node object.

Now let's create a RadDiagram and bind it to our GraphSource:

<Grid> 
    <Grid.Resources> 
        <DataTemplate x:Key="ShapeContentTemplate"> 
            <TextBlock Text="{Binding Title}" /> 
        </DataTemplate> 
        <Style x:Key="OrgChartShapeStyle" TargetType="telerik:RadDiagramShape"> 
            <Setter Property="Position" Value="{Binding Position, Mode=TwoWay}" /> 
            <Setter Property="ContentTemplate" Value="{StaticResource ShapeContentTemplate}" /> 
            <Setter Property="EditTemplate"> 
                <Setter.Value> 
                    <DataTemplate> 
                        <TextBox Text="{Binding Title, Mode=TwoWay}" /> 
                    </DataTemplate> 
                </Setter.Value> 
            </Setter> 
        </Style> 
        <Style TargetType="telerik:RadDiagramConnection"> 
            <Setter Property="ContentTemplate"> 
                <Setter.Value> 
                    <DataTemplate /> 
                </Setter.Value> 
            </Setter> 
            <Setter Property="SourceConnectorPosition" Value="Bottom" /> 
            <Setter Property="TargetConnectorPosition" Value="Top" /> 
        </Style> 
    </Grid.Resources> 
    <Grid.RowDefinitions> 
        <RowDefinition Height="30" /> 
        <RowDefinition Height="*" /> 
    </Grid.RowDefinitions> 
    <StackPanel HorizontalAlignment="Center" Orientation="Horizontal"> 
        <telerik:RadButton Width="100" 
                            Height="30" 
                            Command="telerik:DiagramCommands.Save" 
                            CommandTarget="{Binding ElementName=xDiagram}" 
                            Content="Save" /> 
        <telerik:RadButton Width="100" 
                            Height="30" 
                            Command="telerik:DiagramCommands.Open" 
                            CommandTarget="{Binding ElementName=xDiagram}" 
                            Content="Load" /> 
    </StackPanel> 
    <telerik:RadDiagram x:Name="xDiagram" 
                        Grid.Row="1" 
                        ShapeStyle="{StaticResource OrgChartShapeStyle}"> 
        <telerik:RadDiagram.CommandBindings> 
            <CommandBinding CanExecute="CommandBinding_CanExecute" 
                            Command="telerik:DiagramCommands.Save" 
                            Executed="CommandBinding_Executed_Save" /> 
            <CommandBinding Command="telerik:DiagramCommands.Open" Executed="CommandBinding_Executed_Open" /> 
        </telerik:RadDiagram.CommandBindings> 
    </telerik:RadDiagram> 
</Grid> 

In code-behid we can populate the RadDiagram.GraphSource and use a counter to make sure each Node has a unique ID:

public MainWindow() 
{ 
    InitializeComponent(); 
    BindGraphSource(); 
} 
private void BindGraphSource() 
{ 
    int uniqueIdCounter = 0; 
    GraphSource source = new GraphSource(); 
    OrgItem rootItem = new OrgItem() { Title = "CEO", Position = new Point(200, 20), Id = (uniqueIdCounter++).ToString() }; 
    source.AddNode(rootItem); 
 
    OrgItem unitOne = new OrgItem() { Title = "Unit Manager USA", Position = new Point(100, 100), Id = (uniqueIdCounter++).ToString() }; 
    source.AddNode(unitOne); 
    source.AddLink(new OrgLink(rootItem, unitOne) { Id = (uniqueIdCounter++).ToString() }); 
 
    OrgItem unitTwo = new OrgItem() { Title = "Unit Manager Europe", Position = new Point(300, 100), Id = (uniqueIdCounter++).ToString() }; 
    source.AddNode(unitTwo); 
    source.AddLink(new OrgLink(rootItem, unitTwo) { Id = (uniqueIdCounter++).ToString() }); 
 
    this.xDiagram.GraphSource = source; 
}    
Public Sub New() 
    InitializeComponent() 
    BindGraphSource() 
End Sub 
Private Sub BindGraphSource() 
    Dim uniqueIdCounter As Integer = 0 
    Dim source As New GraphSource() 
    Dim rootItem As New OrgItem() With { 
         .Title = "CEO", 
         .Position = New Point(200, 20), 
         .Id = (System.Math.Max(System.Threading.Interlocked.Increment(uniqueIdCounter), uniqueIdCounter - 1)).ToString() 
    } 
    source.AddNode(rootItem) 
 
    Dim unitOne As New OrgItem() With { 
         .Title = "Unit Manager USA", 
         .Position = New Point(100, 100), 
         .Id = (System.Math.Max(System.Threading.Interlocked.Increment(uniqueIdCounter), uniqueIdCounter - 1)).ToString() 
    } 
    source.AddNode(unitOne) 
    source.AddLink(New OrgLink(rootItem, unitOne) With { 
         .Id = (System.Math.Max(System.Threading.Interlocked.Increment(uniqueIdCounter), uniqueIdCounter - 1)).ToString() 
    }) 
 
    Dim unitTwo As New OrgItem() With { 
         .Title = "Unit Manager Europe", 
         .Position = New Point(300, 100), 
         .Id = (System.Math.Max(System.Threading.Interlocked.Increment(uniqueIdCounter), uniqueIdCounter - 1)).ToString() 
    } 
    source.AddNode(unitTwo) 
    source.AddLink(New OrgLink(rootItem, unitTwo) With { 
         .Id = (System.Math.Max(System.Threading.Interlocked.Increment(uniqueIdCounter), uniqueIdCounter - 1)).ToString() 
    }) 
 
    Me.xDiagram.GraphSource = source 
End Sub 

Fianlly, we have to define the RadDiagram CommandBindings execution methods:

private string diagramXMLString; 
private void CommandBinding_Executed_Save(object sender, ExecutedRoutedEventArgs e) 
{ 
    diagramXMLString = this.xDiagram.Save(); 
} 
 
private void CommandBinding_Executed_Open(object sender, ExecutedRoutedEventArgs e) 
{ 
    if (diagramXMLString != null) 
    { 
        this.xDiagram.Load(diagramXMLString); 
    } 
} 
 
private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e) 
{ 
    e.CanExecute = this.xDiagram.Items.Count > 0; 
} 
Private diagramXMLString As String 
Private Sub CommandBinding_Executed_Save(sender As Object, e As ExecutedRoutedEventArgs) 
    diagramXMLString = Me.xDiagram.Save() 
End Sub 
 
Private Sub CommandBinding_Executed_Open(sender As Object, e As ExecutedRoutedEventArgs) 
    If diagramXMLString IsNot Nothing Then 
        Me.xDiagram.Load(diagramXMLString) 
    End If 
End Sub 
 
Private Sub CommandBinding_CanExecute(sender As Object, e As CanExecuteRoutedEventArgs) 
    e.CanExecute = Me.xDiagram.Items.Count > 0 
End Sub 

The Save command implementation saves the serialized RadDiagram in a string object. This means that you can use this approach in case you need to save your diagramming structures in a database. Once you have the string saved, you can later retrieve it and load the data using the RadDiagram Load(serializationString) method.

Now let's see a possible use case with this set of Diagram configuration and ViewModels: raddiagram-features-serialization-serializable

You can download a runnable project demonstrating a similar approach from the online SDK repository.

Please note that copy/pasting requires additional effort. Pasting a copied node, for example, means that you have to override the AddNode method of the ObservableGraphSourceBase and provide a new unique Id for the new NodeViewModel.

See Also

In this article