WPF TreeView分层数据模板 - 绑定具有多个子集合的对象

66

我正在尝试将我的集合绑定到一个TreeView上,以便所有的分组都显示嵌套的分组,并且每个分组都会显示条目。

如何使用HierarchicalDataTemplate,以使TreeView可以处理SubGroups和Entries集合?

分组显示子组和条目:

Example:
Group1
--Entry
--Entry
Group2
--Group4
----Group1
------Entry
------Entry
----Entry
----Entry
--Entry
--Entry
Group3
--Entry
--Entry

对象:


namespace TaskManager.Domain
{
    public class Entry
    {
        public int Key { get; set; }
        public string Name { get; set; }
    }
}

namespace TaskManager.Domain
{
    public class Group
    {
        public int Key { get; set; }
        public string Name { get; set; }

        public IList<Group> SubGroups { get; set; }
        public IList<Entry> Entries { get; set; }
    }
}

测试数据:


namespace DrillDownView
{
    public class TestData
    {

        public IList<Group> Groups = new List<Group>();

        public void Load()
        {
            Group grp1 = new Group() { Key = 1, Name = "Group 1", SubGroups = new List<Group>(), Entries = new List<Entry>() };
            Group grp2 = new Group() { Key = 2, Name = "Group 2", SubGroups = new List<Group>(), Entries = new List<Entry>() };
            Group grp3 = new Group() { Key = 3, Name = "Group 3", SubGroups = new List<Group>(), Entries = new List<Entry>() };
            Group grp4 = new Group() { Key = 4, Name = "Group 4", SubGroups = new List<Group>(), Entries = new List<Entry>() };

            //grp1
            grp1.Entries.Add(new Entry() { Key=1, Name="Entry number 1" });
            grp1.Entries.Add(new Entry() { Key=2, Name="Entry number 2" });
            grp1.Entries.Add(new Entry() { Key=3,Name="Entry number 3" });

            //grp2
            grp2.Entries.Add(new Entry(){ Key=4, Name = "Entry number 4"});
            grp2.Entries.Add(new Entry(){ Key=5, Name = "Entry number 5"});
            grp2.Entries.Add(new Entry(){ Key=6, Name = "Entry number 6"});

            //grp3
            grp3.Entries.Add(new Entry(){ Key=7, Name = "Entry number 7"});
            grp3.Entries.Add(new Entry(){ Key=8, Name = "Entry number 8"});
            grp3.Entries.Add(new Entry(){ Key=9, Name = "Entry number 9"});

            //grp4
            grp4.Entries.Add(new Entry(){ Key=10, Name = "Entry number 10"});
            grp4.Entries.Add(new Entry(){ Key=11, Name = "Entry number 11"});
            grp4.Entries.Add(new Entry(){ Key=12, Name = "Entry number 12"});

            grp4.SubGroups.Add(grp1);
            grp2.SubGroups.Add(grp4);

            Groups.Add(grp1);
            Groups.Add(grp2);
            Groups.Add(grp3);
        }
    }
}

XAML:


<Window x:Class="DrillDownView.Window2"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TaskManager.Domain;assembly=TaskManager.Domain"
        Title="Window2" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <TreeView Name="GroupView" Grid.Row="0" Grid.Column="0" ItemsSource="{Binding}">
            <TreeView.Resources>
                <HierarchicalDataTemplate DataType="{x:Type local:Group}" ItemsSource="{Binding SubGroups}">
                    <TextBlock Text="{Binding Path=Name}" />
                </HierarchicalDataTemplate>
                <HierarchicalDataTemplate DataType="{x:Type local:Entry}" ItemsSource="{Binding Entries}">
                    <TextBlock Text="{Binding Path=Name}" />
                </HierarchicalDataTemplate>
            </TreeView.Resources>
        </TreeView>
    </Grid>
</Window>

XAML.CS:


public partial class Window2 : Window
{
    public Window2()
    {
        InitializeComponent();
        LoadView();
    }

    private void LoadView()
    {
        TestData data = new TestData();
        data.Load();
        GroupView.ItemsSource = data.Groups;
    }
}

1
非常长的帖子,并且缺少了你可能想要问的问题。 - Gishu
我该如何使用HierarchicalDataTemplate,以便TreeView可以处理SubGroups和Entries集合? - longday
3
已重新格式化并移动了一些代码。如果您不喜欢,可以还原。正在处理您的问题... - Gishu
这是一个错误吗?在YAML和代码中都设置树形项源。我期望的代码会引发异常。 GroupView.ItemsSource = data.Groups - profimedica
4个回答

123

HierarchicalDataTemplate是一种描述“如何呈现某种类型对象以及如何查找该对象下的子节点”的方法。

因此,您需要一个返回此节点“子节点”的单个属性。例如:(如果无法使Group和Entry都派生自公共Node类型)

public class Group{ ....
        public IList<object> Items
        {
            get
            {
                IList<object> childNodes = new List<object>();
                foreach (var group in this.SubGroups)
                    childNodes.Add(group);
                foreach (var entry in this.Entries)
                    childNodes.Add(entry);

                return childNodes;
            }
        }

接下来你不需要为entry使用HierarchicalDataTemplate,因为entry没有子元素。因此,XAML需要更改以使用新的Items属性和一个DataTemplate用于Entry:

<TreeView Name="GroupView" Grid.Row="0" Grid.Column="0" ItemsSource="{Binding}">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type local:Group}" ItemsSource="{Binding Items}">
            <TextBlock Text="{Binding Path=Name}" />
        </HierarchicalDataTemplate>
        <DataTemplate DataType="{x:Type local:Entry}" >
            <TextBlock Text="{Binding Path=Name}" />
        </DataTemplate>
    </TreeView.Resources>
</TreeView>

以下是它的外观。 输出的屏幕截图


@Gishu:你觉得我的替代实现怎么样(请看我的回答)?我更喜欢使用返回类型为IEnumerable,除非我真的需要使用IList。此外,这种方法允许我们利用yield关键字来简化代码。 - Matthew
2
@Matthew.. 很不错的改进。最好的代码就是你不必写的代码 :) +1 - Gishu
那个答案为我节省了大量时间。非常感谢Gishu的帮助。 - Shaik
谢谢你提供这个很好的例子!我唯一建议的是使用接口而不是对象。 - Taras
使用CompositeCollection比将对象添加到List<object>中更好。 - bitman
显示剩余4条评论

12

这是对Gishu的答案的另一种实现,它返回一个 IEnumerable 而不是一个 IList,并使用 yield 关键字来简化代码:

public class Group
{
    ...

    public IEnumerable<object> Items
    {
        get
        {
            foreach (var group in this.SubGroups)
                yield return group;
            foreach (var entry in this.Entries)
                yield return entry;
        }
    }
}

1
谢谢!我卡住了,这帮了我很多忙。 我无法同时正确显示条目和分组。这节省了我很多时间。 - user1799563

11

我认为你已经接近成功了,只需做一点点修改就能相对容易地使它工作...

我建议你创建一个基础的抽象类(或者接口,无论你喜欢哪个),并将其继承/实现到Group和Entry类中...

这样,你就可以在Group对象中暴露一个属性。

public ObservableCollection<ITreeViewItem> Children { get; set; }

在这一点上,您可以决定是否用此替换您的SubGroups和Entries列表,还是仅将它们附加在一起并在属性getter中返回它们...

现在您只需要使用Group或Entry对象来填充Children集合,当对象放置在TreeView中时,HierarchicalDataTemplate将正确呈现。

最后,如果Entry始终是树的“底层”(即没有子项),则不需要为Entry对象定义HierarchicalDataTemplateDataTemplate就足够了。

希望这有所帮助 :)


1
一个基本的工作示例将有助于新手掌握概念。但无论如何,这是一个很好的想法。 - ΩmegaMan
我采纳了您的建议,并使用原始JSON作为输入,使用ITreeViewItem加载通用JSON。您可以在此处看到我的回答:如何在WPF TreeView中显示JSON - ΩmegaMan

3

7
虽然这个回答从理论上回答了问题,但最好在这里包含回答的关键部分,并提供链接作为参考。 - Conrad Frix

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接