如果这些类中的任何一个具有共同的基类,那么您会更容易,例如,您可以将一个DataTemplate 用于多个类。
但是,如果这些模型中的每一个确实不同并且没有足够的共同点,则您需要使用DataTemplateSelector,尽管内置机制可能就足够了。
设置
这是我为重现类似情况而打下的一些基础。每个不同的类都继承自List<object>,因此它具有包含任何类型子级的内置方式,但我不依赖于选择数据模板的通用性。
public class P1 : List<object> {
public P1() {}
public P1( IEnumerable<object> collection ) : base( collection ) {}
}
此外,我的根数据源是List<object> 类型,因此它可以包含任何类型的对象。
Window 的构造函数:
public MainWindow() {
InitializeComponent();
this.DataContext = MyDataSource.GetData(); // in which I construct the tree of parents and children
}
解决方案
首先为每种类型创建HierarchicalDataTemplates。如果任何类型不包含子项,您当然会为它们创建DataTemplates:
<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>
由于每个类都源自List<object>,因此对象本身是子项的来源,而不是属性上的集合,因此我使用ItemsSource="{Binding}"。如果子项位于名为Children 的属性下的集合中,那么它当然是ItemsSource="{Binding Children}"。这仍然允许每个对象在不同的位置拥有其子对象。
在此示例中实现 DataTemplateSelector 的最简单方法是什么都不做。因为我只在数据模板上指定了DataType 而不是x:Key,即使集合是模糊的(List<object>) WPF 仍然会检查底层类型以确定它是否是P1/Q1/等等并找到正确的HierarchicalDataTemplate 使用。我的TreeView 只需看起来像这样:
<TreeView ItemsSource"{Binding}" />
但是,假设为了向您展示 DataTemplateSelector,您不能依赖它与 Type 的隐式匹配。您可以将x:Keys 放在您的数据模板中,例如:
<HierarchicalDataTemplate DataType="{x:Type loc:P1}" x:Key="myKeyforP1"
ItemsSource="{Binding}">
<TextBlock>a P1 object</TextBlock>
</HierarchicalDataTemplate>
然后您的选择器可能会在内部使用Dictionary 来确定给定Type 使用什么资源键(请记住,这是一个幼稚的实现):
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 );
}
}
然后您将选择器添加到资源字典中,并且您的 TreeView 需要分配另一个属性:
<Grid.Resources>
<loc:CustomDataTemplateSelector x:Key="mySelector" />
</Grid.Resources>
<TreeView ItemsSource="{Binding}"
ItemTemplateSelector="{StaticResource mySelector}"></TreeView>
而且因为base.SelectTemplate() 将尝试使用项目的Type 作为资源键,所以您可以为模型设置标准HierarchicalDataTemplate 和仅当您的TreeView 使用自定义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>