It will be easier on you if any of these classes have base classes in common so that, for example, you can use one DataTemplate
for several classes.
However, if each of these models really is different and don't have enough in common, you'll need to use a DataTemplateSelector
, though built-in mechanisms may suffice.
Setup
Here's some of the foundation I made for the sake of recreating a similar situation. Each of the different classes inherits from List<object>
so that it has a built-in way to contain children of any type, but I don't depend on that commonality for choosing datatemplates.
public class P1 : List<object> {
public P1() {}
public P1( IEnumerable<object> collection ) : base( collection ) {}
}
In addition, my root datasource is of type List<object>
so that it can contain any types of objects.
The Window
's constructor:
public MainWindow() {
InitializeComponent();
this.DataContext = MyDataSource.GetData(); // in which I construct the tree of parents and children
}
Solution
Start by making HierarchicalDataTemplate
s for each type. If any of the types do not contain children, you'll of course make DataTemplate
s instead for them:
<HierarchicalDataTemplate DataType="{x:Type loc:P1}"
ItemsSource="{Binding}">
<TextBlock>a P1 object</TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:C1}"
ItemsSource="{Binding}">
<TextBlock>a C1 object</TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:C2}"
ItemsSource="{Binding}">
<TextBlock>a C2 object</TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:Q1}"
ItemsSource="{Binding}">
<TextBlock>a Q1 object</TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:B1}"
ItemsSource="{Binding}">
<TextBlock>a B1 object</TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:B2}"
ItemsSource="{Binding}">
<TextBlock>a B2 object</TextBlock>
</HierarchicalDataTemplate>
Since each class descends from List<object>
, the object itself is the source of children items, rather than a collection on a property, so I use ItemsSource="{Binding}"
. If the child items are in a collection under a property called Children
, it would instead be ItemsSource="{Binding Children}"
, of course. This still allows each object to have its children in a different place.
The simplest way to implement your DataTemplateSelector
in this example is to do nothing. Because I only specified the DataType
and not an x:Key
on the data templates, even though the collections are vague (List<object>
) WPF will still examine the underlying type to determine if it is P1
/Q1
/etc. and find the correct HierarchicalDataTemplate
to use. My TreeView
only has to look like this:
<TreeView ItemsSource"{Binding}" />
However, let's say for the sake of showing you a DataTemplateSelector
that you can't rely on it implicitly matching by Type
. You'd put x:Key
s on your datatemplates, such as this:
<HierarchicalDataTemplate DataType="{x:Type loc:P1}" x:Key="myKeyforP1"
ItemsSource="{Binding}">
<TextBlock>a P1 object</TextBlock>
</HierarchicalDataTemplate>
Then your selector might internally use a Dictionary
to determine what resource key to use for a given Type
(keep in mind that this is a naive implementation):
public class CustomDataTemplateSelector : DataTemplateSelector {
static Dictionary<Type, object> typeToKey = new Dictionary<Type, object>();
static CustomDataTemplateSelector() {
typeToKey[ typeof( P1 ) ] = "myKeyforP1";
}
public override DataTemplate SelectTemplate( object item, DependencyObject container ) {
var element = container as FrameworkElement;
if ( element != null && item != null ) {
var itemtype = item.GetType();
object keyObject;
if ( typeToKey.TryGetValue( itemtype, out keyObject ) ) {
var template = element.TryFindResource( keyObject ) as DataTemplate;
if ( template != null ) {
return template;
}
}
}
return base.SelectTemplate( item, container );
}
}
Then you'd add the selector to a resource dictionary and your TreeView
would need another property assigned:
<Grid.Resources>
<loc:CustomDataTemplateSelector x:Key="mySelector" />
</Grid.Resources>
<TreeView ItemsSource="{Binding}"
ItemTemplateSelector="{StaticResource mySelector}"></TreeView>
And because base.SelectTemplate()
will try to use the item's Type
as a resource key, you can have a standard HierarchicalDataTemplate
for the model and a customized version that will only be used if your TreeView
is using the custom DataTemplateSelector
:
<HierarchicalDataTemplate DataType="{x:Type loc:P1}"
ItemsSource="{Binding}">
<TextBlock>a P1 object</TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:P1}" x:Key="myKeyforP1"
ItemsSource="{Binding}">
<TextBlock>a P1 that looks much different</TextBlock>
</HierarchicalDataTemplate>