为包含多个集合作为子节点的 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 节点构建数据模板的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

VB.NET中treeview的使用

未显示根线时,TreeView 将 +- 号添加到根节点[重复]

MFC Treeview:如何检查 Treeview 是不是已经包含特定的子节点?

Treeview 节点导出到 excel

wpf 自定义treeview 如何获得树节点集合

WPF中treeview模版问题