WPF TreeView typically displays hierarchical tree view by using HierarchicalDataTemplate. This HierarchicalDataTemplate allows you to display different types in different ways. For example, for class A type child node, one might want to display image and name while for class B child node, one might need to display name and size textblock. This usually can be archieved by using DataType attribute.
<HierarchicalDataTemplate x:Key="LargeAreaTemplate" DataType="{x:Type vm:LargeArea}" >
If one needs one hierarchical structure with consistent parent-child relationship, defining DataType in HierarchicalDataTemplate will do the trick. However, what if one needs two different hierarchies in a tree view? If two different categories (let's say, for many-to-many relationship) should be shown in a give tree view, how can it be done with HierarchicalDataTemplate? The following picture shows an example.
There are 2 categories. By Area category shows Age child nodes. By Age category shows Area child nodes.
In a XAML, we can add different hierarchical data templates per each category. For [By Area] view, Area is parent and Age is child node type and vice versa for [By Age] view. If you look at below example, you can see that each category view has different data templates.
<Window x:Class="Tree.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="323" Width="253"
xmlns:vm="clr-namespace:Tree"
>
<Window.Resources>
<vm:MyTemplateSelector x:Key="MyTemplateSelector" />
<!-- AREA VIEW -->
<HierarchicalDataTemplate x:Key="LargeAreaTemplate" DataType="{x:Type vm:LargeArea}"
ItemsSource="{Binding AgeCollection}"
ItemTemplateSelector="{StaticResource MyTemplateSelector}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" : Size=" />
<TextBlock Text="{Binding AreaSize}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="SmallAreaTemplate" DataType="{x:Type vm:SmallArea}"
ItemsSource="{Binding AgeCollection}"
ItemTemplateSelector="{StaticResource MyTemplateSelector}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" : Population=" />
<TextBlock Text="{Binding Population}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="LeafAgeTemplate" DataType="{x:Type vm:Age}"
ItemTemplateSelector="{StaticResource MyTemplateSelector}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<!-- AGE VIEW -->
<HierarchicalDataTemplate x:Key="AgeTemplate" DataType="{x:Type vm:Age}"
ItemsSource="{Binding AreaCollection}"
ItemTemplateSelector="{StaticResource MyTemplateSelector}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="LeafLargeAreaTemplate" DataType="{x:Type vm:LargeArea}"
ItemTemplateSelector="{StaticResource MyTemplateSelector}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" : Size=" />
<TextBlock Text="{Binding AreaSize}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="LeafSmallAreaTemplate" DataType="{x:Type vm:SmallArea}"
ItemTemplateSelector="{StaticResource MyTemplateSelector}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" : Population=" />
<TextBlock Text="{Binding Population}" />
</StackPanel>
</HierarchicalDataTemplate>
<!-- Style -->
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</Window.Resources>
<Grid Height="282" Width="230">
<Button Content="By Area" Height="23" HorizontalAlignment="Left" Margin="20,20,0,0"
Name="btnArea" VerticalAlignment="Top" Width="90" Click="btnArea_Click" />
<Button Content="By Age" Height="23" HorizontalAlignment="Right" Margin="0,20,26,0"
Name="btnAge" VerticalAlignment="Top" Width="90" Click="btnAge_Click" />
<TreeView Name="treeView1" HorizontalAlignment="Left" VerticalAlignment="Top"
Margin="20,49,0,0" Width="184" Height="216"
ItemsSource="{Binding Path=MyItems}"
ItemTemplateSelector="{StaticResource MyTemplateSelector}">
</TreeView>
</Grid>
</Window>
Since TreeView only selects one unique data template at a time for any given type, the sample above is using custom data template selector in ItemTemplateSelector. In a code behind file, the following class is defined.
public class MyTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
MethodInfo mi = container.GetType().GetMethod("FindResource") as MethodInfo;
if (mi != null)
{
if (MainWindow.ViewMode == ViewMode.ByArea)
{
switch (item.ToString())
{
case "Tree.LargeArea":
return mi.Invoke(container, new object[] { "LargeAreaTemplate" }) as DataTemplate;
case "Tree.SmallArea":
return mi.Invoke(container, new object[] { "SmallAreaTemplate" }) as DataTemplate;
case "Tree.Age":
return mi.Invoke(container, new object[] { "LeafAgeTemplate" }) as DataTemplate;
}
}
else
switch (item.ToString())
{
case "Tree.Age":
return mi.Invoke(container, new object[] { "AgeTemplate" }) as DataTemplate;
case "Tree.LargeArea":
return mi.Invoke(container, new object[] { "LeafLargeAreaTemplate" }) as DataTemplate;
case "Tree.SmallArea":
return mi.Invoke(container, new object[] { "LeafSmallAreaTemplate" }) as DataTemplate;
}
}
return null;
}
}
Basically what it does is to find appropriate data template and return it back to TreeView. If null is returned, TreeView searches for appropriate hierarchical data template for the DataType.