Edit this page

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 our online SDK repository here, after navigating to Diagram/MVVM.

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