Вам будет легче, если у любого из этих классов будут общие базовые классы, так что, например, вы можете использовать один 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
}
Solution
Начните с HierarchicalDataTemplate
s для каждого типа.Если какой-либо из типов не содержит дочерние элементы, вы, конечно, вместо них сделаете для них DataTemplate
s:
<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:Key
s на ваши шаблоны данных, например:
<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>