Self-reference hierarchy with Telerik TreeView for Silverlight

by XAML Team | Comments 15

Often we need to display in a TreeView flat, self-referencing data, loaded from a database, that has properties ID and ParentID (or similar) that define the hierarchy. The Telerik TreeView for ASP.NET can automatically bind itself to such data, but our Silverlight TreeView cannot do this out of the box. Fortunately, this “limitation” can be easily avoided with a simple value converter. There is a little trick, however – each data item needs a reference to its parent collection.

Consider the following very simple data object:

public class DataItem : INotifyPropertyChanged
{
    private string text; 
 
 public int ID { get; set; }
    public int ParentID { get; set; }
    public DataItemCollection Owner { get; private set; }
    public string Text
    {
        // Standard INotifyPropertyChanged get/set
    }

    internal void SetOwner(DataItemCollection collection)
    {
        this.Owner = collection;
    }

    // INotifyPropertyChanged implementation goes here
}

 

Those data objects are added into a special DataItemCollection class, that inherits ObservableCollection<T> and overrides SetItem(), InsertItem(), RemoveItem() and ClearItems() methods. In each override we call AdoptItem and DiscardItem, respectively, that set the Owner property of the DataItem class:

public class DataItemCollection : ObservableCollection<DataItem>
{
    protected override void InsertItem(int index, DataItem item)
    {
        this.AdoptItem(item);
        base.InsertItem(index, item);
    }

    protected override void RemoveItem(int index)
    {
        this.DiscardItem(this[index]);
        base.RemoveItem(index);
    }

    protected override void SetItem(int index, DataItem item)
    {
        this.AdoptItem(item);
        base.SetItem(index, item);
    }

    protected override void ClearItems()
    {
        foreach (DataItem item in this)
        {
            this.DiscardItem(item);
        }
        base.ClearItems();
    }

    private void AdoptItem(DataItem item)
    {
        item.SetOwner(this);
    }

    private void DiscardItem(DataItem item)
    {
        item.SetOwner(null);
    }
}

 

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. You should add partial classes in your application that extend the auto-generated according my code above.

Now we are ready to data-bind our RadTreeView:

<UserControl.Resources> <local:HierarchyConverter x:Key="HierarchyConverter" /> <telerik:HierarchicalDataTemplate x:Name="ItemTemplate" ItemsSource="{Binding Converter={StaticResource HierarchyConverter}}"> <TextBlock Text="{Binding Text}" /> </telerik:HierarchicalDataTemplate> </UserControl.Resources> <StackPanel x:Name="LayoutRoot"> <telerikNavigation:RadTreeView x:Name="TreeView1" ItemTemplate="{StaticResource ItemTemplate}" ItemsSource="{Binding Converter={StaticResource HierarchyConverter}}" 
 SelectedValuePath="Text" /> <TextBlock Text="{Binding SelectedValue, ElementName=TreeView1}" /> </StackPanel>

 

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

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)
        {
            return item.Owner.Where(i => i.ParentID == item.ID);
        }

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

        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 TreeView, 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 cases. I did it like this for simplicity, but if you want, you could split the code in two classes.

 

Here is the sample project:

 

Thank you for your time, I hope this article will be of help.

Posted in: howto treeview

15 Comments

Josef Rogovsky
Nice!

This is helpful.

Thank you very much.
Miro Paskov
Great!

I like how this is done with very little code.
Mark
Is this adaptable to a WPF grid?
Tim
This doesn't work (using Silverlight 3.0 and latest Telerik controls - 2009.3).  Also, is there a way to specify a "RadTreeViewItem" instead of a TextBlock for the HierarchicalDataTemplate?
Tim
I found the issue.  Since I changed the "ID" and "ParentID" properties to "object" instead of "int", I needed to change the HeirarchyConverter class from ".Where(i => i.ParentID == item.ID)" to ".Where(i => object.Equals(i.ParentID, item.ID)".  This made the heirarchy show up correctly.  Before everything was coming up as a flat list.
Tim
Ok, one more question.  How can we support drag/drop with this type of data bound to the Tree?  I want to allow the user to modify the "hierarchy" at runtime.
David
Great article but  is posible if my Hierachy has more that two leves? I don´t think because I can have tow elements with samen ID.
hannes
How do you get the collection to update when you insert more items into collection programmatically?
Valeri Hristov
@hannes: Unfortunately the child collections do not raise notification events. If you want to allow dynamic addition of elements I would recommend creating the collections prior data-binding the treeview - this way you will not need the converter.
@David: The example is not limited to two levels. You just need to adjust the item relations in the data source.
@Tim: I guess hannes's answer is relevant for your question too - if you want dynamic hierarchy, build the hierarchical collections before data-binding the treeview.
hannes
Thx for the answer, too bad it wasn't automagic ^_^
Truong Pham
I have tried as your guide but It display my collection 02 times. It display the hierachy collection but it also display the collection as flat data. As following example

Item1
       Item 1.1
              Item 1.1.1
       Item 1.2
              Item 1.2.1
                           Item 1.2.1.1
Item1
Item 1.1
Item 1.1.1
Item 1,2
Item 1.2.1
Item 1.2.1.1

Please give me advise
 
Sameeksha
Awesome this works great! Thanks!
Sameeksha
How can I mark certain dataitems in the tree as checked based on the instance of the data that I am binding to? My tree is in tristatemode, and I need to set the checkboxes as per data.
Prakash
I need to allow dynamic addition of elements to Treeview. On adding new item with specified key and parent key, the node should be added to specified parent. Please help me.

Comments

  1.    
      
      
       
  2. (optional, emails won't be shown on public pages)
  3. (optional)
Read more articles by XAML Team - or - read latest articles in Developer Tools