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="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://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="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://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.
Comments
Anonymous
March 07, 2007
Is there any way of binding some TreeCollection to WPF TreeView? If we dont know how many levels this collection will have?Anonymous
April 30, 2007
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.Anonymous
July 02, 2007
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?Anonymous
July 04, 2007
The comment has been removedAnonymous
July 05, 2007
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!Anonymous
September 17, 2007
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 EricAnonymous
October 04, 2007
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 ADAnonymous
July 12, 2011
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 . ThanksAnonymous
August 28, 2012
Good Working Example. Will be more useful for freshers. :)