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>
我最初认为您也可以通过将 TreeViewItem
的 x: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 中完成。
我想将多个不同的列表绑定到 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>
我最初认为您也可以通过将 TreeViewItem
的 x: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 中完成。