TreeView and HierarchicalDataTemplate, Step-by-Step

I’ve never found TreeView to be terribly confusing by itself.  But usually I want to data bind a TreeView to a collection with some hierarchy, which leads me to HierarchicalDataTemplate, which didn’t always just write itself for me.  If you look at it in steps, though, there really is a pretty nice progression from ListBox to TreeView.  Like a lot of my posts, this goes way too deep if you just want to create a quick TreeView.  But it’s useful to look at it if you want the HierarchicalDataTemplate to just write itself …

 

First, start with the example of a simple ListBox:

 

<ListBox>

    <sys:String>Western Conference</sys:String>

    <sys:String>Eastern Conference</sys:String>

</ListBox>

clip_image001

 

We can add a trivial ItemTemplate to it:

 

<ListBox>

    <ListBox.ItemTemplate>

        <DataTemplate>

            <TextBlock Foreground="Red" Text="{Binding}" />

        </DataTemplate>

    </ListBox.ItemTemplate>

    <sys:String>Western Conference</sys:String>

    <sys:String>Eastern Conference</sys:String>

</ListBox>

clip_image002

 

… and now the list box items are red.

 

A ListBox is an ItemsControl, and what ItemsControls like to do is wrap their items in a container (more on that here).  So the above essentially becomes:

 

<ListBox>

   

    <ListBoxItem Content="Eastern Conference">

        <ListBoxItem.ContentTemplate>

            <DataTemplate x:Name="_template1">

                <TextBlock Foreground="Red" Text="{Binding}" />

            </DataTemplate>

        </ListBoxItem.ContentTemplate>

    </ListBoxItem>

    <ListBoxItem Content="Western Conference"

                ContentTemplate="{Binding ElementName=_template1}" />

</ListBox>

clip_image003

 

That is, for each item in the ListBox, a ListBoxItem is created.  The ListBoxItem’s Content property is bound to the item, and the ListBoxItem’s ContentTemplate property is bound to the ListBox’s ItemTemplate.

 

On to TreeView …

 

A TreeView is also an ItemsControl; it generates TreeViewItem controls for its items.  And actually, if you just take the original example and change “ListBox” to “TreeView”, it looks almost the same:

 

<TreeView>

    <sys:String>Eastern Conference</sys:String>

    <sys:String>Western Conference</sys:String>

</TreeView>

clip_image004

 

You can similarly set an explicit item template:

 

<TreeView>

    <TreeView.ItemTemplate>

        <DataTemplate>

            <TextBlock Foreground='Red' Text='{Binding}' />

        </DataTemplate>

    </TreeView.ItemTemplate>

 

    <sys:String>Western Conference</sys:String>

    <sys:String>Eastern Conference</sys:String>

</TreeView>

clip_image005

 

The next step here caught be by surprise when I first saw it.  The TreeView is going to create a TreeViewItem for each of its items (the two strings).  A TreeViewItem is a HeaderedItemsControl, which is an ItemsControl (just like ListBox and TreeView), but also has a Header (and HeaderTemplate) property.  (The Header is the part of the tree view item that you always see, right next to the expand/collapse button.)  The Header/HeaderTemplate of a HeaderedItemsControl is analogous to the Content/ContentTemplate of a ContentControl.  So, whereas the ListBox items were bound to each ListBoxItem’s Content property, in the TreeView case the items are bound to the TreeViewItem’s Header property.  Similarly, the TreeView’s ItemTemplate property is bound to the TreeViewItem’s HeaderTemplate property.  And in the end we essentially have:

 

<TreeView>

   

    <TreeViewItem Header='Western Conference'>

       

        <TreeViewItem.HeaderTemplate>

            <DataTemplate x:Name='_template2'>

                <TextBlock Foreground='Red' Text='{Binding}' />

        </DataTemplate>

        </TreeViewItem.HeaderTemplate>

       

    </TreeViewItem>

 

    <TreeViewItem Header='Eastern Conference'

                 HeaderTemplate='{Binding ElementName=_template2}' />

</TreeView>

clip_image006

 

Now what we need is some hierarchy.  Those items are the MLS conferences, and there’s supposed to be teams in those conferences.  Here’s the full data:

 

var western = new Conference("Western")

{

    Teams =

    {

        new Team("Club Deportivo Chivas USA"),

        new Team("Colorado Rapids"),

        new Team("FC Dallas"),

        new Team("Houston Dynamo"),

        new Team("Los Angeles Galaxy"),

        new Team("Real Salt Lake"),

        new Team("San Jose Earthquakes"),

        new Team("Seattle Sounders FC"),

        new Team("Portland 2011"),

        new Team("Vancouver 2011")

    }

};

var eastern = new Conference("Eastern")

{

    Teams =

    {

        new Team("Chicago Fire"),

        new Team("Columbus Crew"),

        new Team("D.C. United"),

        new Team("Kansas City Wizards"),

        new Team("New York Red Bulls"),

        new Team("New England Revolution"),

        new Team("Toronto FC"),

        new Team("Philadelphia Union 2010")

   }

};

var league = new Collection<Conference>() { western, eastern };

DataContext = new

{

    WesternConference = western,

    EasternConference = eastern,

    League = league

};

 

(Note that the DataContext now has this sample data.)

 

And now we can show all the teams with some explicit hierarchy:

 

<TreeView>

    <TreeViewItem Header='Western Conference'

         ItemsSource="{Binding WesternConference.Teams}">

        <TreeViewItem.ItemTemplate>

            <DataTemplate>

                <!-- Team name -->

                <TextBlock Text="{Binding Name}" />

            </DataTemplate>

        </TreeViewItem.ItemTemplate>

    </TreeViewItem>

 

    <TreeViewItem Header='Eastern Conference'

         ItemsSource="{Binding EasternConference.Teams}">

        <TreeViewItem.ItemTemplate>

            <DataTemplate>

                <!-- Team name -->

                <TextBlock Text="{Binding Name}" />

            </DataTemplate>

        </TreeViewItem.ItemTemplate>

    </TreeViewItem>

</TreeView>

 

clip_image007

Of course, what you really want to do is bind the TreeView itself to the hierarchical collection (the League of the DataContext).  I.e.:

 

<TreeView ItemsSource="{Binding League}" />

clip_image008

 

 

As you can see, there’s two items, as you’d expect, just like a ListBox.  Also just like a ListBox, it doesn’t show anything except for the ToString() of the Conference object.  So we need to give it an ItemTemplate to show the conference Name:

 

<TreeView ItemsSource="{Binding League}">

    <TreeView.ItemTemplate>

        <DataTemplate>

            <TextBlock Foreground="Red" Text="{Binding Name}" />

        </DataTemplate>

    </TreeView.ItemTemplate>

</TreeView>

clip_image009

 

Now, recall that the TreeView here is creating two TreeViewItems, binding the TreeViewItem’s Header to the Conference object, and setting the TreeViewItem’s HeaderTemplate to the TreeView’s ItemTemplate.  Next question is, how do we get the TreeViewItem’s ItemsSource bound to Conference.Teams?  That’s where the HierarchicalDataTemplate comes in. 

 

A HierarchicalDataTemplate is a DataTemplate with some extra properties.  But if you don’t use the extra properties, it’s no different than DataTemplate.  For example, change the last markup from DataTemplate to HierarchicalDataTemplate, and nothing changes:

 

<TreeView ItemsSource="{Binding League}">

    <TreeView.ItemTemplate>

        <HierarchicalDataTemplate >

            <TextBlock Foreground="Red" Text="{Binding Name}" />

        </HierarchicalDataTemplate>

    </TreeView.ItemTemplate>

</TreeView>

clip_image010

 

But HierarchicalDataTemplate adds two key properties:  ItemsSource and ItemTemplate.  The ItemsSource gets mapped to the TreeViewItem.ItemsSource, and the ItemTemplate gets mapped to the TreeViewItem.ItemTemplate.  So now we can show the conferences and the teams:

 

<TreeView ItemsSource="{Binding League}">

   

    <!-- Conference teamplate -->

    <TreeView.ItemTemplate>

        <HierarchicalDataTemplate ItemsSource="{Binding Teams}">

            <TextBlock Foreground="Red" Text="{Binding Name}" />

           

            <!-- Team template -->

            <HierarchicalDataTemplate.ItemTemplate>

                <DataTemplate>

                    <TextBlock Text="{Binding Name}" />

                </DataTemplate>

            </HierarchicalDataTemplate.ItemTemplate>

           

        </HierarchicalDataTemplate>

    </TreeView.ItemTemplate>

   

</TreeView>

clip_image011

 

And if you want to show one level deeper in the hierarchy, you can change that team DataTemplate to a HierarchicalDataTemplate:

 

<TreeView ItemsSource="{Binding League}">

   

    <!-- Conference template -->

    <TreeView.ItemTemplate>

        <HierarchicalDataTemplate ItemsSource="{Binding Teams}">

            <TextBlock Foreground="Red" Text="{Binding Name}" />

           

            <!-- Team template -->

            <HierarchicalDataTemplate.ItemTemplate>

                <HierarchicalDataTemplate ItemsSource="{Binding Players}">

                    <TextBlock Text="{Binding Name}" />

 

                    <!-- Player template -->

                    <HierarchicalDataTemplate.ItemTemplate>

                        <DataTemplate>

                            <TextBlock Text="{Binding}" />

                        </DataTemplate>

                    </HierarchicalDataTemplate.ItemTemplate>

                   

                </HierarchicalDataTemplate>

            </HierarchicalDataTemplate.ItemTemplate>

           

        </HierarchicalDataTemplate>

    </TreeView.ItemTemplate>

   

</TreeView>

 

 

clip_image012

In the end, the bottom line that I keep in mind when I’m writing a HierarchicalDataTemplate, is that it’s the template for the TreeViewItem.Header, and it lets me set the TreeViewItem’s ItemsSource and ItemTemplate properties.

TreeViewStepByStep.zip