【问题标题】:How to add conditional template of TreeView.Resources in XAML?如何在 XAML 中添加 TreeView.Resources 的条件模板?
【发布时间】:2019-11-01 16:40:35
【问题描述】:

我想要一个有条件的 XAML 来选择是 TreeView.Resources 还是其他。

我正在使用MaterialDesignInXAML toolkitCard,里面是TreeView

假设我有一个 Fruits 集合,其中包含另一个名为 Trees 的集合。

<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);
            }
        }
    }
}

问题

这组代码将首先检查Trees,而不添加Fruits。所以这条线if (key.Fruits.Count() == 0) 将永远返回true。有什么我可以做的吗?我错过了什么吗?

我发现 an answer from another thread 可能会有所帮助,但是,我不知道这将如何在 ViewModel 上工作,我不知道如何通过绑定的 Property 选择某个设置器。

【问题讨论】:

  • 到底想达到什么目的?为了在StyleTemplate 之间切换,您可以使用StyleSelectorDataTemplateSelector。在大多数情况下,TriggerDataTrigger 也可以使用。
  • @BionicCode,我想使用条件语句在TreeView.Resources 中使用不同的ItemSource
  • 请更具体。你的ItemsSource 是什么?条件是什么?最常见的场景是绑定到视图模型。如果是这种情况并且条件与 UI 无关,那么您只需直接在视图模型中将不同的集合分配给源属性。如果条件与 UI 相关(例如,按下按钮),您可以使用 ICommand 来触发源属性的所述重新分配,或者使用 DataTrigger。我不能给你一个例子,因为你的问题非常广泛。它缺乏细节。细节会影响最佳方法。
  • @BionicCode,请查看更新后的问题。
  • 谢谢。您的问题是一个很好的例子,说明提供更多细节将如何导致不同的解决方案,因为许多假设都被消除了。此外,更多细节表明您最初的方法或策略是错误的。我发布了一个答案。

标签: c# wpf xaml


【解决方案1】:

这里有一些选择。我将根据数据结构(模型)设计向您展示两个建议。

第一个建议

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

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; }
}

Category.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>

【讨论】:

  • 从这个&lt;NodeTemplateSelector x:Key="NodeTemplateSelector"&gt;,这是你创建的NodeTemplateSelector 类吗?
  • 是的。这就是在 XAML 中创建对象实例的方法。由于实例是在 ResourceDictionary> 中定义的,因此需要 Key
  • 关于您的错误:您必须修改我的 XAML 代码以满足您的条件。您需要删除 Window 标记。我刚刚添加它是为了展示如何分配 DataContext 并使用 ResourceDictionary 来声明模板。
猜你喜欢
  • 1970-01-01
  • 2019-10-12
  • 1970-01-01
  • 2014-05-10
  • 2020-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-06
相关资源
最近更新 更多