Edit this page

Bind RadTreeView to Self-Referencing Data

This tutorial will show you how to display a RadTreeView with flat, self-referencing data, loaded from a database, that has properties ID and ParentID (or similar) which define the hierarchy.

Consider the following very simple data object:

Example 1: Defining the DataItem class

public class DataItem
{
    public int Id
    {
        get;
        set;
    }
    public int ParentId
    {
        get;
        set;
    }
    public string Text
    {
        get;
        set;
    }

    public DataItemCollection OwnerCollection
    {
        get;
        protected set;
    }

    internal void SetOwnerCollection(DataItemCollection collection)
    {
        this.OwnerCollection = collection;
    }
}
Public Class DataItem
Inherits ViewModelBase

Public Property Id As Integer

Public Property ParentId As Integer

Public Property Text As String

Public Property OwnerCollection As DataItemCollection

Friend Sub SetOwnerCollection(ByVal collection As DataItemCollection)
    Me.OwnerCollection = collection
End Sub

End Class

Those data objects are added into a special DataItemCollection class, that inherits ObservableCollection and implements an AssociatedItem property that holds the root of each node.

Example 2: Defining DataItemCollection

 public class DataItemCollection : ObservableCollection<DataItem>
{
    public DataItemCollection()
        : base()
    {
    }

    public DataItemCollection(IEnumerable<DataItem> collection)
        : base(collection)
    {
    }

    public DataItem AssociatedItem
    {
        get;
        protected set;
    }

    public void SetAssociatedItem(DataItem item)
    {
        this.AssociatedItem = item;
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        base.OnCollectionChanged(e);
        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            foreach (DataItem item in e.NewItems)
            {
                if (this.AssociatedItem != null && item.ParentId != this.AssociatedItem.Id)
                {
                    item.ParentId = this.AssociatedItem.Id;
                }                    
            }
        }
    }
}
    Public Class DataItemCollection
        Inherits ObservableCollection(Of DataItem)

        Public Sub New()
        End Sub

        Public Sub New(ByVal collection As IEnumerable(Of DataItem))
        End Sub

        Public Property AssociatedItem As DataItem

        Public Sub SetAssociatedItem(ByVal item As DataItem)
            Me.AssociatedItem = item
        End Sub

        Protected Overrides Sub OnCollectionChanged(ByVal e As NotifyCollectionChangedEventArgs)
            MyBase.OnCollectionChanged(e)
            If e.Action = NotifyCollectionChangedAction.Add Then
                For Each item As DataItem In e.NewItems
                    If Me.AssociatedItem IsNot Nothing AndAlso item.ParentId <> Me.AssociatedItem.Id Then
                        item.ParentId = Me.AssociatedItem.Id
                    End If
                Next
            End If
    End Sub

End Class

Normally when you load your data objects from a service in your application, you will have auto-generated partial classes, that are relatively easy to extend.

Now we are ready to data-bind our RadTreeView:

Example 3: Defining the resources

<example:HierarchyConverter x:Key="HierarchyConverter" />

<HierarchicalDataTemplate x:Key="ItemTemplate"
  ItemsSource="{Binding Converter={StaticResource HierarchyConverter}}">
    <TextBlock Text="{Binding Text}" />
</HierarchicalDataTemplate>

Example 4: Defining the RadTreeView

<telerik:RadTreeView x:Name="radTreeView"
 ItemTemplate="{StaticResource ItemTemplate}"
 ItemsSource="{Binding Converter={StaticResource HierarchyConverter}}"/>

There is one non-standard thing: all ItemsSource bindings are made through a ValueConverter. This ValueConverter will create the "real" hierarchy for us:

Example 5: Defining the HierarchyConverter

public class HierarchyConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        // We are binding an item
        DataItem item = value as DataItem;
        if (item != null)
        {   
            var children = item.OwnerCollection.Where(i => i.ParentId == item.Id);
            var collection = new DataItemCollection(children);
            collection.SetAssociatedItem(item);
            return collection;
        }

        // We are binding the treeview
        DataItemCollection items = value as DataItemCollection;
        if (items != null)
        {
            var children = items.Where(i => i.ParentId == 0);
            return new DataItemCollection(children);
        }
        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
    Public Class HierarchyConverter
        Inherits IValueConverter

        Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object
            Dim item As DataItem = TryCast(value, DataItem)
            If item IsNot Nothing Then
                Dim children = item.OwnerCollection.Where(Function(i) i.ParentId = item.Id)
                Dim collection = New DataItemCollection(children)
                collection.SetAssociatedItem(item)
                Return collection
            End If

            Dim items As DataItemCollection = TryCast(value, DataItemCollection)
            If items IsNot Nothing Then
                Dim children = items.Where(Function(i) i.ParentId = 0)
                Return New DataItemCollection(children)
            End If

            Return Nothing
        End Function

        Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object
            Throw New NotImplementedException()
    End Function

End Class

When a DataItem object is passed as value, we are binding a TreeViewItem, so the Convert() method will return all DataItem objects from the Owner collection that have ParentID equal to the ID of the passed DataItem. When a DataItemCollection is passed, we are binding the RadTreeView, so the Convert() method will return the root-level DataItem objects, that have ParentID=0. Of course, it is up to you to decide whether you want a single, or separate converters for both of the cases. It is done in this way for simplicity, but if you want, you could split the code into two classes.

Example 6: Populating the RadTreeView

public MainWindow()
{
    InitializeComponent();

    var source =  new DataItemCollection()
    {                
            new DataItem () { Text = "Item 1", Id = 1, ParentId = 0 },
            new DataItem () { Text = "Item 2", Id = 2, ParentId = 0 },
            new DataItem () { Text = "Item 3", Id = 3, ParentId = 0 },
            new DataItem () { Text = "Item 1.1", Id = 5, ParentId = 1 },
            new DataItem () { Text = "Item 1.2", Id = 6, ParentId = 1 },
            new DataItem () { Text = "Item 1.3", Id = 7, ParentId = 1 },
            new DataItem () { Text = "Item 2.1", Id = 8, ParentId = 2 },
            new DataItem () { Text = "Item 2.2", Id = 9, ParentId = 2 },
            new DataItem () { Text = "Item 2.3", Id = 10, ParentId = 2 },
            new DataItem () { Text = "Item 3.1", Id = 11, ParentId = 3 },
            new DataItem () { Text = "Item 3.2", Id = 12, ParentId = 3 },
            new DataItem () { Text = "Item 3.3", Id = 13, ParentId = 3, }                 
    };

    foreach (var item in source)
    {
        item.SetOwnerCollection(source);
    }

    this.DataContext = source;
}
Public Sub MainWindow()
    InitializeComponent()
    Dim source = New DataItemCollection() From {
        New DataItem() With {.Text = "Item 1", .Id = 1, .ParentId = 0}, 
        New DataItem() With {.Text = "Item 2", .Id = 2, .ParentId = 0},
         New DataItem() With {.Text = "Item 3", .Id = 3, .ParentId = 0}, 
         New DataItem() With {.Text = "Item 1.1", .Id = 5, .ParentId = 1}, 
         New DataItem() With {.Text = "Item 1.2", .Id = 6, .ParentId = 1}, 
         New DataItem() With {.Text = "Item 1.3", .Id = 7, .ParentId = 1}, 
         New DataItem() With {.Text = "Item 2.1", .Id = 8, .ParentId = 2}, 
         New DataItem() With {.Text = "Item 2.2", .Id = 9, .ParentId = 2}, 
         New DataItem() With {.Text = "Item 2.3", .Id = 10, .ParentId = 2}, 
         New DataItem() With {.Text = "Item 3.1", .Id = 11, .ParentId = 3}, 
         New DataItem() With {.Text = "Item 3.2", .Id = 12, .ParentId = 3}, 
         New DataItem() With {.Text = "Item 3.3", .Id = 13, .ParentId = 3}}

    For Each item In source
        item.SetOwnerCollection(source)
    Next

    Me.DataContext = source
End Sub

Image 1: Self-Referencing RadTreeView

Self referencing RadTreeView

You can check out this example in the RadTreeView SDK examples or in the SDK Samples Browser that provides a more convenient approach in exploring and executing the examples in the Telerik XAML SDK repository. The SDK Samples Browser application is available for download from this link.

See Also

Is this article helpful? Yes / No
Thank you for your feedback!

Give article feedback

Tell us how we can improve this article

close
Dummy