tags:

views:

91

answers:

1

I would like to know how to bind a custom datatype to a treeview.

The datatype is basically arraylists of objects that contain other arraylists. Access to would look something like this.

foreach (DeviceGroup dg in system.deviceGroups)
    {
        foreach (DeviceType dt in dg.deviceTypes)
        {
            foreach (DeviceInstance di in dt.deviceInstances)
            {

            }
        }
    }

I would like the treeview to look something like this:

DeviceGroup1

 --> DeviceType1
      --DeviceInstance1
      --DeviceInstance2
 --> DeviceType2
      --DeviceInstance1

DeviceGroup2

 --> DeviceType1
      --DeviceInstance1
 --> DeviceType2
+1  A: 

Ok this is where the HierarchicalDataTemplate will save you. The trick is you will need to use two different hierarchical templates, since you have a three-level hierarchy here. I have constructed a simple UserControl to illustrate. First, here is some code-behind creating model data similar to what you have:

public partial class ThreeLevelTreeView : UserControl
{
    public ArrayList DeviceGroups { get; private set; }

    public ThreeLevelTreeView()
    {
        DeviceInstance inst1 = new DeviceInstance() { Name = "Instance1" };
        DeviceInstance inst2 = new DeviceInstance() { Name = "Instance2" };
        DeviceInstance inst3 = new DeviceInstance() { Name = "Instance3" };
        DeviceInstance inst4 = new DeviceInstance() { Name = "Instance4" };

        DeviceType type1 = new DeviceType() { Name = "Type1", DeviceInstances = new ArrayList() { inst1, inst2 } };
        DeviceType type2 = new DeviceType() { Name = "Type2", DeviceInstances = new ArrayList() { inst3 } };
        DeviceType type3 = new DeviceType() { Name = "Type3", DeviceInstances = new ArrayList() { inst4 } };
        DeviceType type4 = new DeviceType() { Name = "Type4" };

        DeviceGroup group1 = new DeviceGroup() { Name = "Group1", DeviceTypes = new ArrayList() { type1, type2 } };
        DeviceGroup group2 = new DeviceGroup() { Name = "Group2", DeviceTypes = new ArrayList() { type3, type4 } };

        DeviceGroups = new ArrayList() { group1, group2 };

        InitializeComponent();
    }
}

public class DeviceGroup
{
    public string Name { get; set; }
    public ArrayList DeviceTypes { get; set; }
}

public class DeviceType
{
    public string Name { get; set; }
    public ArrayList DeviceInstances { get; set; }
}

public class DeviceInstance
{
    public string Name { get; set; }
}

Nothing difficult here, but note that you should use ObservableCollection instead of ArrayList if you want to add and remove from your collections dynamically. Now let's look at the XAML for this control:

<UserControl x:Class="TestWpfApplication.ThreeLevelTreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestWpfApplication"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<TreeView ItemsSource="{Binding DeviceGroups}">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding DeviceTypes}">
            <HierarchicalDataTemplate.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding DeviceInstances}">
                    <TextBlock Text="{Binding Name}"/>
                </HierarchicalDataTemplate>
            </HierarchicalDataTemplate.ItemTemplate>
            <TextBlock Text="{Binding Name}"/>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

And here is the result:

alt text

Charlie
Thank you so much for your response. Your example works great when the class is contained within ThreeLevelTreeView, but I'm having some difficulty understanding how to bind an external class to the datacontext of the ThreeLevelTreeView. How would I go about doing that?
Robert
You don't have to bind the DataContext. You could simply set it in code like this: yourTreeView.DataContext = yourClass.
Charlie
I was wondering how I would bind data to the treeview without using the user control. I have to reroute all the event handlers through the user control and I find that kind of tedious. I have copied out the TreeView part of the user control and placed it in my MainWindow.xaml. I bind the data in the Window_Loaded event like this: _systemTreeView.DataContext = DeviceGroups;But nothing appears in the TreeView.
Robert
Switch from using ArrayList to ObservableCollection. ArrayList does not support automatic change notifications so your TreeView doesn't know any items are being added. This worked in my example because I loaded the items and set the DataContext before InitializeComponent was called in the constructor. Since you are binding in the Loaded event the TreeView is never getting the notification. But ObservableCollection supports automatic change notifications so any items you add will update the binding.
Charlie
I have switched to ObservableCollection, but it still does not work. Adding/Removing objects changes dynamically which is great, but when I try to add a brand new datacontext, the whole treeview disappears. I think it may have something to do with how the binding works in the xaml for ThreeLevelTreeView, but I'm not sure.
Robert
I guess what I'm really asking is can you do this without making it a User Control?
Robert
Yes of course. If you wanted to put it in a Window you could. Just make sure the Window's DataContext is set to itself and the top level items collection is located on the Window. Either way it will be no different than nesting the UserControl inside the Window, just less encapsulated.
Charlie
How do I change the text color of the instance names to red?
Robert
Change the innermost data-template's TextBlock to this:<TextBlock Text="{Binding Name}" Foreground="Red"/>
Charlie