New to Telerik UI for WPF? Download free 30-day trial

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; 
    } 
} 

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; 
                }                     
            } 
        } 
    } 
} 

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(); 
    } 
} 

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; 
} 

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.