WPF Treeview绑定多个不同的列表

时间:2019-02-18 10:11:26

标签: c# wpf treeview hierarchicaldatatemplate

我想将多个不同的列表绑定到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。

2 个答案:

答案 0 :(得分:1)

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

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

然后更新您的LightDistributor。请注意,我是如何使List<T>属性仅具有getter的功能,以便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>

我最初认为您也可以通过将x:Array中的TreeViewItem声明为资源(每个项分别用于“硬件”,“输入”,“输出”),然后将其设置为HierarchicalTemplate的ItemsSource来实现适用于LightDistributor。但这是行不通的,因为似乎没有一种方法可以为我们要显示的每个LightDistributor克隆此x:Array

答案 1 :(得分:1)

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

无论如何,不​​是创建控件数组,而是创建数据对象数组。在这种情况下,类型sys:String可以正常工作,并具有额外的好处,我们稍后也可以将其用作TreeViewItem标头文本:

    <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类中的子属性。 TreeView的第二级将获得分配为其DataContext的以下字符串之一,因此我们将为其创建样式,并使用触发器通过父级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>

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

<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中完成。