WPF Treeview 绑定多个不同的列表

WPF Treeview Binding multiple, different Lists

我想将多个不同的列表绑定到 WPF 中的 TreeView。我查找了其他一些解决方案,但找不到解决我的问题的任何帮助。 This answer 非常接近,但不是我想要的。

我试过上面链接的解决方案,但它在 TreeView 中只显示两个级别。 我不知道如何在 TreeView 中将每个列表的名称显示为父级。

我要显示的对象如下所示:

public class LightDistributor
{
  public string Description { get; set; }
  // ...

  public List<Field> Hardware { get; set; }
  public List<Type> Inputs { get; set; }
  public List<Type> Outputs { get; set; }
}

public class Field
{
  public string Fieldname { get; set; }
  // ...
}

public class Type
{
  public string TypeDescription { get; set; }
  // ...
}

和 XAML:

<TreeView ItemsSource="{Binding LightDistributors}">
  <TreeView.ItemTemplate>
    <HierarchicalDataTemplate DataType="{x:Type data:LightDistributor}" ItemsSource="{Binding Hardware}">
       <TextBlock Text="{Binding Description}" />
       <HierarchicalDataTemplate.ItemTemplate>
          <DataTemplate DataType="{x:Type data:Field}">
               <TextBlock Text="{Binding Description}" />
          </DataTemplate>
       </HierarchicalDataTemplate.ItemTemplate>
    </HierarchicalDataTemplate>
  </TreeView.ItemTemplate>   
</TreeView>

我希望 Treeview 显示的内容:

LightDistributor - LongFloor
    | Hardware
        - Field1
        - Field2
        - Field3
    | Inputs
        - InputTypeA
        - InputTypeB
    | Outputs
        - OutputTypeY
        - OutputTypeZ

目前的样子:

LightDistributor - LongFloor
        - Field1
        - Field2
        - Field3

根据 SelectedItem,显示带有更多参数的 UserControl。

添加 NamedSection,将名称与项目列表分组:

public class NamedSection
{
    public string Name { get; set; }
    public IReadOnlyList<object> Items { get; set; }
}

然后更新您的 LightDistributor。请注意我是如何将 List<T> 属性设置为 getter-only 的,以便 NamedSection 可以正确捕获构造参考。

public class LightDistributor
{
    public string Description { get; set; }
    // ...

    public List<Field> Hardware { get; } = new List<Field>();
    public List<Type> Inputs { get; } = new List<Type>();
    public List<Type> Outputs { get; } = new List<Type>();

    public List<NamedSection> Sections { get; }

    public LightDistributor()
    {
        this.Sections = new List<NamedSection>()
        {
            new NamedSection() { Name = "Hardware", Items = this.Hardware },
            new NamedSection() { Name = "Inputs", Items = this.Inputs },
            new NamedSection() { Name = "Outputs", Items = this.Outputs },
        };
    }
}

那么你的XAML:

<TreeView ItemsSource="{Binding LightDistributors}">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type local:LightDistributor}" ItemsSource="{Binding Sections}">
            <TextBlock Text="{Binding Description}" />
        </HierarchicalDataTemplate>
        <HierarchicalDataTemplate DataType="{x:Type local:NamedSection}" ItemsSource="{Binding Items}">
            <TextBlock Text="{Binding Name}"/>
        </HierarchicalDataTemplate>
        <DataTemplate DataType="{x:Type local:Field}">
            <TextBlock Text="{Binding Fieldname}"/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:Type}">
            <TextBlock Text="{Binding TypeDescription}"/>
        </DataTemplate>
    </TreeView.Resources>
</TreeView>

我最初认为您也可以通过将 TreeViewItemx:Array 声明为资源(硬件、输入、输出各有一个项目)然后将其设置为 ItemsSource 来实现此目的LightDistributor 的 HierarchicalTemplate。但这不起作用,因为似乎没有办法为我们要显示的每个 LightDistributor 克隆此 x:Array

在此处添加另一个答案,展示如何在纯 XAML 中进行操作。这几乎完全基于 canton7 最初的第二个解决方案,该解决方案非常接近但正在创建一个正在回收的 TreeViewItems 数组。通常设置 x:Shared="False" 应该可以解决这个问题,事实上在他的情况下它不起作用对我来说似乎是一个 WPF 错误。

无论如何,不​​要创建控件数组,而是创建数据数组 objects。在这种情况下,键入 sys:String 可以正常工作,还有一个额外的好处,即我们稍后还可以将其用作 TreeViewItem header 文本:

    <x:Array x:Key="DistributorItems" Type="{x:Type sys:String}">
        <sys:String>Hardware</sys:String>
        <sys:String>Inputs</sys:String>
        <sys:String>Outputs</sys:String>
    </x:Array>

这些代表您的 LightDistributor class 中的 child 属性。 TreeView 的第二层将获得这些字符串之一分配为其 DataContext,因此我们将为这些字符串创建一个样式,并使用触发器通过 parent TreeViewItem 的 DataContext 相应地设置 ItemsSource

    <Style x:Key="TreeViewItemStyle" TargetType="TreeViewItem">
        <Style.Triggers>
            <Trigger Property="DataContext" Value="Hardware">
                <Setter Property="ItemsSource" Value="{Binding DataContext.Hardware, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}, AncestorLevel=1}}" />
            </Trigger>
            <Trigger Property="DataContext" Value="Inputs">
                <Setter Property="ItemsSource" Value="{Binding DataContext.Inputs, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}, AncestorLevel=1}}" />
            </Trigger>
                <Trigger Property="DataContext" Value="Outputs">
                <Setter Property="ItemsSource" Value="{Binding DataContext.Outputs, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}, AncestorLevel=1}}" />
            </Trigger>
        </Style.Triggers>
    </Style>

其余代码与 canton7 的原始代码基本相同,除了我将 LightDistributor 的 ItemContainerStyle 设置为我在上面创建的样式以相应地设置 ItemsSource:

<TreeView ItemsSource="{Binding LightDistributors}">
    <TreeView.Resources>

        <HierarchicalDataTemplate DataType="{x:Type vm:LightDistributor}"
                                  ItemsSource="{Binding Source={StaticResource DistributorItems}}"
                                  ItemContainerStyle="{StaticResource TreeViewItemStyle}">
            <TextBlock Text="{Binding Description}"/>
        </HierarchicalDataTemplate>

        <DataTemplate DataType="{x:Type vm:Field}">
            <TextBlock Text="{Binding Fieldname}"/>
        </DataTemplate>

        <DataTemplate DataType="{x:Type vm:Type}">
            <TextBlock Text="{Binding TypeDescription}"/>
        </DataTemplate>

    </TreeView.Resources>
</TreeView>

现在仅仅因为这个有效并不意味着它是一个好的解决方案,我仍然非常认为 canton7 的第一个解决方案是一个更好的解决方案。只是把它扔在那里,以表明它毕竟可以在纯 XAML 中完成。