为包含多个集合作为子节点的 TreeView 节点构建数据模板的正确方法是啥?
Posted
技术标签:
【中文标题】为包含多个集合作为子节点的 TreeView 节点构建数据模板的正确方法是啥?【英文标题】:What's the proper way to build a data template for a TreeView node that contains multiple collections as children?为包含多个集合作为子节点的 TreeView 节点构建数据模板的正确方法是什么? 【发布时间】:2021-09-24 20:38:59 【问题描述】:我有一个包含多个属性集合的类:
class Foo
public ObservableCollection<Bar> Bars get; set;
public ObservableCollection<Baz> Bazzes get; set;
我试图在TreeView
中显示它,其中Foo
节点位于根节点,然后在它下面是Bars
集合的节点,其中包含每个Bar
元素作为子节点,Bazzes
集合也是如此。但我似乎无法正确获取数据模板。我设法得到的最接近的是这样的:
<HierarchicalDataTemplate DataType="x:Type local:Foo">
<TreeViewItem Header="Root">
<TreeViewItem Header="Bars" ItemsSource="Binding Path=Bars"/>
<TreeViewItem Header="Bazzes" ItemsSource="Binding Path=Bazzes"/>
</TreeViewItem>
</HierarchicalDataTemplate>
<DataTemplate DataType="x:Type local:Bar">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Binding Name" />
<TextBlock Text=" (" Foreground="Blue" />
<TextBlock Text="Binding Type" Foreground="Blue" />
<TextBlock Text=")" Foreground="Blue" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="x:Type local:Baz">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Binding Name" />
</StackPanel>
</DataTemplate>
这显示了一个带有节点的分层树,我可以通过单击小三角形打开以显示子项目,但是当我尝试单击任何项目时,它会选择整个 Foo
子项 作为一大选择。我假设这是因为包含集合的节点已集成到Foo
的模板中,因此它以某种方式将它们都视为一个大节点?但是如果不这样做,我不知道如何让集合显示为子节点。
什么是我正在寻找的设置类型的正确方法,因为这显然不太正确?
【问题讨论】:
【参考方案1】:您的实施存在几个基本问题。第一个是 Tree 只是一个映射到绑定数据结构的映射,该结构预计是一棵树。首先我们必须让你的 Foo 类成为一棵树......
public class BarBazBase
public string Name get; set;
public string Type get; set;
public class Bar : BarBazBase
public string BarSpecial get; set;
public class Baz : BarBazBase
public string BazSpecial get; set;
public class Foo : ObservableCollection<ObservableCollection<BarBazBase>>
public ObservableCollection<BarBazBase> Bars get; set; = new ObservableCollection<BarBazBase>();
public ObservableCollection<BarBazBase> Bazzes get; set; = new ObservableCollection<BarBazBase>();
public Foo()
Add(Bars);
Add(Bazzes);
接下来,我们需要为每种类型的树节点使用不同的模板。因此我们需要一个数据模板选择器
public class BasBazTemplateSelector : DataTemplateSelector
public override DataTemplate SelectTemplate(object item, DependencyObject container)
FrameworkElement fe = container as FrameworkElement;
if(item is Foo)
return fe.FindResource("TreeHeader") as DataTemplate;
if (item is ObservableCollection<BarBazBase> baseCollection)
if (baseCollection.Count > 0 && baseCollection[0] is Bar)
return fe.FindResource("BarHeader") as DataTemplate;
else if (baseCollection.Count > 0 && baseCollection[0] is Baz)
return fe.FindResource("BazHeader") as DataTemplate;
else
return null;
else if (item is Bar)
return fe.FindResource("BarItemTemplate") as DataTemplate;
else if (item is Baz)
return fe.FindResource("BazItemTemplate") as DataTemplate;
else
return null;
最后,我们准备好将所有东西放在 XAML 中...
<TreeView x:Name="treeView" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Beige"
ItemsSource="Binding Root" ItemTemplateSelector="StaticResource BasBazTemplateSelector">
<TreeView.Resources>
<HierarchicalDataTemplate x:Key="TreeHeader" ItemsSource="Binding" >
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="BazHeader" ItemsSource="Binding">
<Label>Baz</Label>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="BarHeader" ItemsSource="Binding">
<Label>Bar</Label>
</HierarchicalDataTemplate>
<DataTemplate x:Key="BarItemTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Binding Name" />
<TextBlock Text=" (" Foreground="Blue" />
<TextBlock Text="Binding Type" Foreground="Blue" />
<TextBlock Text=")" Foreground="Blue" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="BazItemTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Binding Name" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
一些松散的结局......
<Window.Resources>
<local:BasBazTemplateSelector x:Key="BasBazTemplateSelector"/>
</Window.Resources>
public Foo Root get; set;
Root = new Foo();
Root.Bars.Add(new Bar() Name = "a", Type = "a0", BarSpecial = "a bar" );
Root.Bars.Add(new Bar() Name = "b", Type = "b0", BarSpecial = "another bar" );
Root.Bazzes.Add(new Baz() Name = "c", Type = "c0", BazSpecial = "a baz" );
Root.Bazzes.Add(new Baz() Name = "d", Type = "d0", BazSpecial = "another baz" );
这是结果...
【讨论】:
这有点工作,但有没有办法做到这一点而不将所有元素硬塞到一个公共基类中?这感觉有点hacky。可以保持异构吗? @MasonWheeler 是的。这种将树视图映射到非树数据结构的类型一直在发生。您缺少的是视图模型。MVVM 模式中的连接元素。因此,在这种方法中,您的 Foo 类保持不变,并且您创建了一个新类,例如 FooViewModel,它是一个引用您的模型的树。 FooViewModel 然后成为树的数据源。我本可以为你写一个视图模型,但这需要更多的代码。以上是关于为包含多个集合作为子节点的 TreeView 节点构建数据模板的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章
未显示根线时,TreeView 将 +- 号添加到根节点[重复]