如何在XAML中添加TreeView.Resources的条件模板



我想要一个条件XAML来选择这个TreeView.Resources还是另一个。

我使用的是MaterialDesignInXAML工具包的Card,里面是TreeView

假设我有一个水果的集合,里面有另一个集合,名为

<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type domain:Tree}" ItemsSource="{Binding Fruits}">
// tree information here ...
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type domain:Fruit}">
// fruit information here ...
</DataTemplate>
</TreeView.Resources>

这里的问题是,有些水果不是从树上长出来的。所以我想要的是拥有另一个资源,但仍然会继续使用模板。

<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type domain:Fruit}">
// fruit information here
</HierarchicalDataTemplate>
</TreeView.Resources>

我希望我的输出看起来像这样:

> Apple Tree
- Apples
> Mango Tree
- Mangoes
> Watermelon

编辑

我使用了@BionicCode的第二个建议,即使用DataTemplateSelector。我将此添加到我的XAML中:

<UserControl DataContext="{Binding ViewModel}">
<UserControl.Resources>
<domain:DtmSelector x:Key="DtmSelector">
<domain:DtmSelector.WithTree>
<HierarchicalDataTemplate DataType="{x:Type domain:Tree}" ItemsSource="{Binding Fruits}">
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type domain:Fruit}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</domain:DtmSelector.WithTree>
<domain:DtmSelector.WithoutTree>
<DataTemplate DataType="{x:Type domain:Tree}">
<TextBlock Text="{Binding Fruits[0].Name}"/>
</DataTemplate>
</domain:DtmSelector.WithoutTree>
</domain:DtmSelector>
</UserControl.Resources>
<TreeView
ItemsSource="{Binding Trees}"
ItemTemplateSelector="{StaticResource DtmSelector}"                
/>
</UserControl>

在我的DtmSelector.cs:中

public class DtmSelector: DataTemplateSelector
{
public DataTemplate WithTrees { get; set; }
public DataTemplate WithoutTrees { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Tree key = item as Tree;
if (key.Fruits.Count() == 0)
{
return WithTrees;
}
else
{
if (key.Fruits.Select(c=>c.Name).Contains(key.Name))
{
return WithTrees;
}
else
{
return WithoutTrees;
}
}
}
}

我的ViewModel的概念是这样的:

foreach (var item in fruits)
{
if ( item.Tree == null)
{
if (!Trees.Select(c => c.Name).Contains(item.Name))
{
Trees.Add(new Tree(item.Name));
}
foreach (var tree in Trees.ToList())
{
if (tree.Name == item.Name)
{
tree.Fruits.Add(item);
}
}
}
else
{
if (!Trees.Select(c => c.Name).Contains(item.Tree.Name))
{
Trees.Add(item.Tree);
}
foreach (var tree in Trees.ToList())
{
if (tree.Name == item.Tree.Name)
{
tree.Fruits.Add(item);
}
}
}
}

问题

这组代码将在不添加Fruits的情况下首先检查Trees。所以这行if (key.Fruits.Count() == 0)将始终返回true。有什么我能做的吗?我是不是错过了什么?

我从另一个线程中找到了一个可能有帮助的答案,然而,我不知道这将如何在ViewModel上工作,也不知道我如何通过绑定的Property选择某个setter。

这里有一些选项。我将向您展示两个基于数据结构(模型(设计的建议。

第一个建议

对完整的树结构使用单一的数据类型:

ViewModel.cs

class ViewModel : INotifyPropertyChanged
{
ViewModel()
{
this.Fruits = new ObservableCollection<Fruit>()
{
new Fruit("Apple Tree", new ObservableCollection<Fruit>() {new Fruit("Apples")}),
new Fruit("Mango Tree", new ObservableCollection<Fruit>() {new Fruit("Mangos")}),
new Fruit("Watermelon")
}
ObservableCollection<Fruit> fruits;
ObservableCollection<Fruit> Fruits
{
get => this.fruits;
set
{
this.fruits = value;
OnPropertyChanged();
}
}
// INotifyPropertyChanged implementation here...
}

Fruit.cs(数据模型(

class Fruit : INotifyPropertyChanged
{
Fruit(string name) : this(name, new List<Fruit>())
{
}
Fruit(string name, IEnumerable<Fruit> children)
{
this.Name = name;
this.Children = new ObservableCollection<Fruit>(children ?? new List<Fruit>());
}
string name;
string Name 
{
get => this.name;
set
{
this.name = value;
OnPropertyChanged();
}
}
ObservableCollection<Fruit> children;
ObservableCollection<Fruit> Children 
{
get => this.children;
set
{
this.children = value;
OnPropertyChanged();
}
}
// INotifyPropertyChanged implementation here...
}

TreeViewXAML

<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<TreeView ItemsSource={Binding Fruits}>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type domain:Fruit}" ItemsSource="{Binding Children}">
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type domain:Fruit}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Window>

第二个建议

如果不同的角色(节点(需要不同的类型,则必须使用DataTemplateSelector:

ViewModel.cs

class ViewModel : INotifyPropertyChanged
{
ViewModel()
{
this.Fruits = new ObservableCollection<INode>()
{
new Category("Apple Tree", new ObservableCollection<INode>() {new Fruit("Apples")}),
new Category("Mango Tree", new ObservableCollection<INode>() {new Fruit("Mangos")}),
new Fruit("Watermelon")
}
ObservableCollection<INode> fruits;
ObservableCollection<INode> Fruits
{
get => this.fruits;
set
{
this.fruits = value;
OnPropertyChanged();
}
}
// INotifyPropertyChanged implementation here...
}

INode.cs(数据模型(

// Common base type for the node collection and all tree nodes
interface INode: INotifyPropertyChanged
{
string Name {get; set; }
ObservableCollection<INode> Children { get; set; }
}

类别.cs(数据模型(

class Category : INode
{
Category(string name) : this(name, new List<INode>())
{
}
Category(string name, IEnumerable<INode> children) 
{
this.Name = name;
this.Children = new ObservableCollection<INode>(children ?? new List<INode>());
}
string name;
string Name 
{
get => this.name;
set
{
this.name = value;
OnPropertyChanged();
}
}
ObservableCollection<INode> children;
ObservableCollection<INode> Children 
{
get => this.children;
set
{
this.children = value;
OnPropertyChanged();
}
}
// INotifyPropertyChanged implementation here...
}

Fruit.cs(数据模型(

class Fruit : INode
{
Fruit(string name) : this(name, new List<INode>())
{
}
Fruit(string name, IEnumerable<INode> children)
{
this.Name = name;
this.Children = new ObservableCollection<INode>(children ?? new List<INode>());
}
string name;
string Name 
{
get => this.name;
set
{
this.name = value;
OnPropertyChanged();
}
}
ObservableCollection<INode> children;
ObservableCollection<INode> Children 
{
get => this.children;
set
{
this.children = value;
OnPropertyChanged();
}
}
// INotifyPropertyChanged implementation here...
}

NodeTemplateSelector.cs

class NodeTemplateSelector : DataTemplateSelector
{
public DataTemlplate CategoryNodeTemplate { get; set; }
public DataTemlplate FruitNodeTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
switch (item)
{
case Category _:
return this.CategoryNodeTemplate;
case Fruit _:
return this.FruitNodeTemplate;
default:
return this.FruitNodeTemplate;
}
}
}

TreeViewXAML

<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<Window.Resources>
<NodeTemplateSelector x:Key="NodeTemplateSelector">
<NodeTemplateSelector.CategoryNodeTemplate>
<HierarchicalDataTemplate DataType="{x:Type Category}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</NodeTemplateSelector.CategoryNodeTemplate>
<NodeTemplateSelector.FruitNodeTemplate>
<DataTemplate DataType="{x:Type Fruit}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</NodeTemplateSelector.FruitNodeTemplate>
</NodeTemplateSelector>
</Window.Resources>
<TreeView ItemsSource="{Binding Fruits}" 
ItemTemplateSelector="{StaticResource NodeTemplateSelector}"  />
</Window>

最新更新