I have a WPF application which uses a TreeView
, inside that TreeView
there are multiple HierarchicalDataTemplates
/DataTemplates
for different types, each containing a ContentControl
with a specific Template
, like so:
TreeView
|- HierarchicalDataTemplate for Type a
| |- ContentControl
|
|- DataTemplate for Type b
|- ContentControl
The type b
is built like this:
b
|-integer c
|-object d
d
can be anything from an integer to a string, but it can also be a class containing a list. In that case I want to display the list of d
using a HierarchicalDataTemplate
inside the TreeView
described above.
Is there a way to do that, or do I lose all connection to the hierarchy of the TreeView
as soon as I enter the DataTemplate
/ContentControl
/Template
?
For complex scenarios like this, you can implement a custom DataTemplateSelector
. From your description I assume a data types like these for A
and B
, with properties for C
and D
:
public class A
{
}
public class B
{
public B(int c, object d)
{
C = c;
D = d;
}
public int C { get; }
public object D { get; }
}
You could create custom data templates for each type and purpose. For B
, there would be both a DataTemplate
for the regular types and a HierarchicalDataTemplate
for when D
is a collection:
<TreeView ItemsSource="{Binding MyItems}">
<TreeView.ItemTemplateSelector>
<local:CustomDataTemplateSelector/>
</TreeView.ItemTemplateSelector>
<TreeView.Resources>
<DataTemplate x:Key="ATemplate"
DataType="{x:Type local:A}">
<TextBlock Text="This is an A."/>
</DataTemplate>
<DataTemplate x:Key="BTemplate" DataType="{x:Type local:B}">
<StackPanel>
<TextBlock Text="{Binding C}"/>
<TextBlock Text="{Binding D}"/>
</StackPanel>
</DataTemplate>
<HierarchicalDataTemplate x:Key="BHierarchicalTemplate"
DataType="{x:Type local:B}"
ItemsSource="{Binding D}">
<TextBlock Text="{Binding C}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
The x:Key
s are needed to resolve to the data templates using the DataTemplateSelector
. In this case, we would check if an item is of type A
and use the ATemplate
. If it is B
, we check whether its template needs to be hierarchical or not by inspecting property D
. If it is a collection - or in more general terms an IEnumerable
, we use the hierarchical template. However, be aware that some types like string
are enumerable, too, so we need to make a separate check.
public class CustomDataTemplateSelector : DataTemplateSelector
{
private const string ATemplateName = "ATemplate";
private const string BTemplateName = "BTemplate";
private const string BHierarchicalTemplateName = "BHierarchicalTemplate";
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (!(container is FrameworkElement frameworkElement))
return base.SelectTemplate(item, container);
// >= C# 6
//switch (item)
//{
// case A:
// return FindDataTemplate(frameworkElement, ATemplateName);
// case B b when b.D is string:
// return FindDataTemplate(frameworkElement, BTemplateName);
// case B b when b.D is IEnumerable:
// return FindDataTemplate(frameworkElement, BHierarchicalTemplateName);
// case B:
// return FindDataTemplate(frameworkElement, BTemplateName);
// default:
// return base.SelectTemplate(item, container);
//}
// >= C# 8
return item switch
{
A => FindDataTemplate(frameworkElement, ATemplateName),
B { D: string } => FindDataTemplate(frameworkElement, BTemplateName),
B { D: IEnumerable } => FindDataTemplate(frameworkElement, BHierarchicalTemplateName),
B => FindDataTemplate(frameworkElement, BTemplateName),
_ => base.SelectTemplate(item, container)
};
}
private static DataTemplate FindDataTemplate(FrameworkElement frameworkElement, string key)
{
return (DataTemplate)frameworkElement.FindResource(key);
}
}
Whether you create constants for the template names or build the keys from the type names or expose properties to assign the data templates is up to your requirements.