A TreeView, a HierarchicalDataTemplate, and a 2D collection walk into a bar …


Bea has a handy post describing how to group items in a collection using a CollectionViewSource.  I was looking at that, and a post on the WPF forum from markovuksanovic, and for fun created a version of Bea’s example that uses a 2D collection instead of the CollectionViewSource.  (I found a bunch of HierarchicalDataTemplate examples using CollectionViewSource or XmlDataProvider, but couldn’t find any using nested collections.)  Anyway, here’s the result …


 


First, by 2D collection, I mean a collection whose items are themselves collections, like a 2D array.  The “parent” collection here is a collection of AnimalCategory objects, each of which has a category name and a collection of Animal objects for that category.  So AnimalCategory looks like (all of this is in a namespace named “HierarchicalDataTemplateTest”):


 


namespace HierarchicalDataTemplateTest


{


    ...


    public class AnimalCategory


 


        private string _category;


        public string Category


        {


            get { return _category; }


            set { _category = value; }


        }


 


        private ObservableCollection<Animal> _animals;


        public ObservableCollection<Animal> Animals


        {


            get


            {


                if (_animals == null)


                    _animals = new ObservableCollection<Animal>();


 


                return _animals;


            }


        }


 


        public AnimalCategory()


        {


        }


 


        public AnimalCategory(


                    string category,


                    ObservableCollection<Animal> animals)


        {


            _category = category;


            _animals = animals;


        }


 


    }


    ...


}


 


… and Animal looks like:


 


namespace HierarchicalDataTemplateTest


{


    ...


    public class Animal


    {


        private string _name;


        public string Name


        {


            get { return _name; }


            set { _name = value; }


        }


 


        public Animal()


        {


        }


 


        public Animal(string name)


        {


            _name = name;


        }


 


    }


    ...


}


 


… and these get used in a sample Window application, whose code looks like:


 


public partial class Window1 : System.Windows.Window


{


    static public ObservableCollection<AnimalCategory> AnimalCategories


        = new ObservableCollection<AnimalCategory>();


 


    public Window1()


    {


        InitializeComponent();


 


        ObservableCollection<Animal> animals = new ObservableCollection<Animal>();


        animals.Add(new Animal("California Newt"));


        animals.Add(new Animal("Tomato Frog"));


        animals.Add(new Animal("Green Tree Frog"));


        AnimalCategories.Add( new AnimalCategory("Amphibians", animals) );


 


        animals = new ObservableCollection<Animal>();


        animals.Add(new Animal("Golden Silk Spider"));


        animals.Add(new Animal("Black Widow Spider"));


        AnimalCategories.Add(new AnimalCategory("Spiders", animals));


    }



 


 


That is, our Window1 has an collection named AnimalCategories of AnimalCategory objects, and each of those has a collection of Animal objects.


 


The markup in our Window displays these in a hierarchy – animals in their categories – using a TreeView.  In this case, the TreeView is bound to the static AnimalCategories collection that we created in the above code:


 


<Window x:Class="HierarchicalDataTemplate.Window1"


    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"


    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"


    Title="HierarchicalDataTemplate" Height="300" Width="300"


    xmlns:local="clr-namespace:HierarchicalDataTemplateTest"


    >


 


  <!-- Create a TreeView, and have it source data from


       the AnimalCategories collection -->


  <TreeView ItemsSource="{x:Static local:Window1.AnimalCategories}">


 


    <!-- Specify the template that will display a node


         from AnimalCategories.  I.e., one each for “Amphibians”


         and “Spiders” in this sample.  It will get its nested


         items from the "Animals" property of each item -->


    <TreeView.ItemTemplate>


      <HierarchicalDataTemplate ItemsSource="{Binding Path=Animals}">


 


        <!-- Display the AnimalCategory by showing it's Category string -->


        <TextBlock FontWeight="Bold" Text="{Binding Path=Category}" />


 


        <!-- Specify the nested template for the individual Animal items


             that are within the AnimalCategories.  E.g. “California Newt”, etc. -->


        <HierarchicalDataTemplate.ItemTemplate>


          <DataTemplate>


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


          </DataTemplate>


        </HierarchicalDataTemplate.ItemTemplate>


       


      </HierarchicalDataTemplate>


    </TreeView.ItemTemplate>


  </TreeView>


</Window>


 


(Note that the "xmlns:local='clr-namespace:HierarchicalDataTemplateTest'" is a reference to the CLR namespace that holds the Animal and AnimalCategory classes.) 


 


The end result is:


 



 



 


 


 


Just to make it more interesting, let’s play with some different options.  Recall that TreeView is an ItemsControl.  ItemsControl can get its items from the ItemsSource property, as the above example shows.  But ItemsControl also has its own built-in collection, which is the Items property.  So instead of the code creating a special collection for the AnimalCategories objects, it could just add the AnimalCategory objects to the Items property.  That is, make this change in the markup (the yellow part is new):


 


<TreeView ItemsSource="{x:Static local:Window1.AnimalCategories}" Name="TreeView1">


 


… and update Window1.xaml.cs to use the TreeView’s Items collection:


 


public partial class Window1 : System.Windows.Window


{


    static public ObservableCollection<AnimalCategory> AnimalCategories


        = new ObservableCollection<AnimalCategory>();


 


    public Window1()


    {


        InitializeComponent();


 


        ObservableCollection<Animal> animals = new ObservableCollection<Animal>();


        animals.Add(new Animal("California Newt"));


        animals.Add(new Animal("Tomato Frog"));


        animals.Add(new Animal("Green Tree Frog"));


        AnimalCategories.Add( new AnimalCategory("Amphibians", animals) );


        TreeView1.Items.Add(new AnimalCategory("Amphibians", animals));


 


        animals = new ObservableCollection<Animal>();


        animals.Add(new Animal("Golden Silk Spider"));


        animals.Add(new Animal("Black Widow Spider"));


        AnimalCategories.Add(new AnimalCategory("Spiders", animals));


        TreeView1.Items.Add(new AnimalCategory("Spiders", animals));


    }


 


}


 


 


… and you get the same application.


 


 


Finally, one last sample.  The above code is still “new”-ing a lot of objects, and creating objects is what Xaml was invented for.  So reduce the Window1 code to its minimal form:


 


public partial class Window1 : System.Windows.Window


{


    static public ObservableCollection<AnimalCategory> AnimalCategories


        = new ObservableCollection<AnimalCategory>();


 


    public Window1()


    {


        InitializeComponent();


 


        ObservableCollection<Animal> animals = new ObservableCollection<Animal>();


        animals.Add(new Animal("California Newt"));


        animals.Add(new Animal("Tomato Frog"));


        animals.Add(new Animal("Green Tree Frog"));


        TreeView1.Items.Add(new AnimalCategory("Amphibians", animals));


 


        animals = new ObservableCollection<Animal>();


        animals.Add(new Animal("Golden Silk Spider"));


        animals.Add(new Animal("Black Widow Spider"));


        TreeView1.Items.Add(new AnimalCategory("Spiders", animals));


    }


 


}


 


… and create those animals in the Xaml instead (updated lines in yellow):


 


<Window x:Class="HierarchicalDataTemplate.Window1"


    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"


    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"


    Title="HierarchicalDataTemplate" Height="300" Width="300"


    xmlns:local="clr-namespace:HierarchicalDataTemplateTest"


    >


 


  <!-- Create a TreeView, and have it source data from


       the AnimalCategories collection -->


  <TreeView Name="TreeView1">


 


    <!-- Specify the template that will display a node


         from AnimalCategories.  It will get its nested


         items from the "Animals" property of each item -->


    <TreeView.ItemTemplate>


      <HierarchicalDataTemplate ItemsSource="{Binding Path=Animals}">


 


        <!-- Display the AnimalCategory by showing it's Category string -->


        <TextBlock FontWeight="Bold" Text="{Binding Path=Category}" />


 


        <!-- Specify the nested template for the individual Animal items


             that are within the AnimalCategory items. -->


        <HierarchicalDataTemplate.ItemTemplate>


          <DataTemplate>


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


          </DataTemplate>


        </HierarchicalDataTemplate.ItemTemplate>


       


      </HierarchicalDataTemplate>


    </TreeView.ItemTemplate>


 


    <local:AnimalCategory Category="Amphibians">


      <local:AnimalCategory.Animals>


        <local:Animal Name="California Newt" />


        <local:Animal Name="Tomato Frog" />


        <local:Animal Name="Green Tree Frog" />


      </local:AnimalCategory.Animals>


    </local:AnimalCategory>


 


    <local:AnimalCategory Category="Spiders">


      <local:AnimalCategory.Animals>


        <local:Animal Name="Golden Silk Spider" />


        <local:Animal Name="Black Widow Spider" />


      </local:AnimalCategory.Animals>


    </local:AnimalCategory>


 


  </TreeView>


       


</Window>


 


Note here that the <AnimalCategory> tags are directly under the <TreeView> tag; since content of a <TreeView> goes automatically to the Items property, this markup is equivalent to the previous “TreeView.Items.Add…” code.


 


 

hdt.jpg

Comments (9)
  1. zajda82 says:

    Is there any way of binding some TreeCollection to WPF TreeView? If we dont know how many levels this collection will have?

  2. Re: “Is there any way of binding some TreeCollection to WPF TreeView? If we dont know how many levels this collection will have?”

    Yes, TreeView expands to any depth.  If you look at the ItemTemplate for the TreeView above, you see it is a HierarchicalDataTemplate with an ItemsSource:

    <

    HierarchicalDataTemplate ItemsSource={Binding Path=Animals}>

    That ItemsSource property explains how, at each level of the tree view, to find the next level.

    This post from Bea (http://www.beacosta.com/2006/02/how-do-i-display-grouped-data-in.html) and this from Karsten (http://blogs.msdn.com/karstenj/archive/2005/11/02/488420.aspx) have more info on TreeView and HierarchicalDataTemplate.

  3. kidsysco says:

    Could not get the example to work, it does not care for the DataTemplate tag nested in the HeirarchicalDataTemplate.ItemTemplate tag.

    Was this example tested?

  4. kidsysco says:

    OK I got this example to build. I think that it is important to deliver folks at least a little info on the namespace delcared at the top of the XAML file. I do have a fundamental understanding of WPF and I can manage to get alot done with it but I did not realize that the XAML needs a reference to the namespace that it is operating in. I guess that just sounds weird to me and I do not quite understand why. I will go ahead and read those sections of the WPF spec again to see if I can figure out why.

    xmlns:local="clr-namespace:HierarchicalDataTemplateTest"

    I had to change that line to …

    xmlns:local="clr-namespace:myNameSpaceForThisXamlFile"

    Thanks for your help Mike!

  5. Sorry kidsysco, you’re right; I should have called that out and included the namespace in the .cs part of the sample.  I’ve updated it now.

    Thanks for posting the comment!

  6. Remora says:

    I try hard to load data only when user click on leaf.

    I have succeeded in creating Treeview data in one shot but data are very heavy to load and I’d like to go down in tree only when needed …

    Ideas guys ?

    Thanks

    Eric

  7. anildhan says:

    Hello Mike,

    How do we design HierarchicalDataTemplate for nested collections.

    For example

    <Collection>

       <Collection>

           <Collection>

    The above stuff is just a raw example.

    But we can have objects of same types nested inside each other.

    for example,

    Node having list of Nodes.

    Could you please help on this.

    Regards

    AD

  8. Simon Lee says:

    Hello Guys

    Can u please tell me how to add another layer of lists , so that i can have a 3D array instead of a 2D .

    I tried to add Itemtemplates but i could not get it .

    Thanks

  9. P.Dinesh Kumar says:

    Good Working Example. Will be more useful for freshers. 🙂

Comments are closed.

Skip to main content