如何将分层列表 <T> 绑定到 WPF TreeView

How to bind hierarchical list<T> to WPF TreeView

我有一个分层类型 Category,我想将其放入 TreeView。嵌套层数是无限的。数据存储在具有 hierarchyid 字段的数据库中。

Class定义

public class Category
{
    public Category()
    {
        NestedCategories = new List<Category>();
    }

    public string CategoryParentID { get; set; }
    public string CategoryHID { get; set; }
    public int CategoryID { get; set; }
    public string CategoryName { get; set; }
    public string CategoryValueType { get; set; }
    public DateTime LastTimeUsed { get; set; }

    public List<Category> NestedCategories
    {
        get; set;
    }

    public void AddChild(Category cat)
    {
        NestedCategories.Add(cat);
    }

    public void RemoveChild(Category cat)
    {
        NestedCategories.Remove(cat);
    }

    public List<Category> GetAllChild()
    {
        return NestedCategories;
    }
}

首先,我从 table 中获取所有数据并将其放入结构化列表中。我在调试器中检查了结果,它确实包含了所有级别的类别。

public CategorySelector()
{
    InitializeComponent();
    catctrl = new CategoryController();

    Categories = CategoriesExpanded();

    DataContext = this;
}

private readonly CategoryController catctrl;

public List<Category> Categories { get; set; }

private List<Category> CategoriesExpanded()
{
    List <Category> list = catctrl.GetAllCategories();
    foreach (Category cvo in GetAllCat(list))
    {
        foreach (Category newparent in GetAllCat(list))
        {
            if (newparent.CategoryHID.ToString().Equals(cvo.CategoryParentID.ToString()))
            {
                list.Remove(cvo);
                newparent.AddChild(cvo);
                break;
            }
        }
    }
    return list;
}

private List<Category> GetAllCat(List<Category> list)
{
    List<Category> result = new List<Category>();
    foreach (Category child in list)
    {
        result.AddRange(GetNestedCat(child));
    }
    return result;
}

private List<Category> GetNestedCat(Category cat)
{
    List<Category> result = new List<Category>();
    result.Add(cat);
    foreach (Category child in cat.NestedCategories)
    {
        result.AddRange(GetNestedCat(child));
    }
    return result;
}

然后我在XAML中添加了绑定,问题就出现了。我尝试了不同的组合,但我得到的只是第一层显示。

<mah:MetroWindow x:Class="appname.Views.CategorySelector"
        xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:localmodels="clr-namespace:appname.Models"
                 mc:Ignorable="d"
        .....
        <TreeView Name="TV_Categories" Grid.Row="5" FontSize="16" ItemsSource="{Binding Categories}" DisplayMemberPath="CategoryName">
            <TreeView.Resources>
                <HierarchicalDataTemplate DataType="{x:Type localmodels:Category}" ItemsSource="{Binding NestedCategories}" >
                    <TextBlock Text="{Binding CategoryName}" />
                </HierarchicalDataTemplate>
            </TreeView.Resources>
        </TreeView>
    </Grid>
</mah:MetroWindow>

那我做错了什么?谢谢。

我认为您使用的 XAML 语言版本有误。有多个 XAML 语言版本。 WPF 仅完全支持 2006 版本。 2009 版本为 only partially supported.

In WPF, you can use XAML 2009 features, but only for XAML that is not WPF markup-compiled. Markup-compiled XAML and the BAML form of XAML do not currently support the XAML 2009 language keywords and features.

这些是正确的 2006 语言 XML 命名空间:

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

在您的 DataType 定义中,您在 属性 元素上使用 xmlns,即 2009 language feature.

XAML 2009 can support XAML namespace (xmlns) definitions on property elements; however, XAML 2006 only supports xmlns definitions on object elements.

如果您的项目不满足上述限制,则不能在 WPF 中使用它。相反,您可以在对象元素上声明本地 XML 名称空间,例如您的顶级元素(此处为 Window)并使用 x:Type 标记扩展或简单地使用 DataType="localmodels:Category",例如:

<Window x:Class="YourApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:localmodels="clr-namespace:YourApp"
<HierarchicalDataTemplate DataType="{x:Type localmodels:Category}" ItemsSource="{Binding NestedCategories}" >
   <TextBlock Text="{Binding CategoryName}" />
</HierarchicalDataTemplate>

更新,我找到了根本原因。 如果您设置 DisplayMemberPath 这将导致 TreeView 应用仅显示ToString() 相应 属性 值的文本。它不知道您的嵌套类别集合。

现在,如果除了设置 DisplayMemberPath 之外还直接将数据模板分配给 TreeView.ItemTemplate,您将收到一个异常,说明您不能同时使用两者。

System.InvalidOperationException: 'Cannot set both "DisplayMemberPath" and "ItemTemplate".'

但是,如果您在 Resources 中定义数据模板,没有例外,它会自动失败并应用 DisplayMemberPath 模板,并且这就是为什么只显示一级的原因。

为了解决这个问题,只需删除 DisplayMemberPath 并且所有这些变体都有效。

<TreeView Name="TV_Categories" Grid.Row="5" FontSize="16" ItemsSource="{Binding Categories}">
   <TreeView.Resources>
      <HierarchicalDataTemplate DataType="{x:Type local:Category}" ItemsSource="{Binding NestedCategories}" >
         <TextBlock Text="{Binding CategoryName}" />
      </HierarchicalDataTemplate>
   </TreeView.Resources>
</TreeView>
<TreeView Name="TV_Categories" Grid.Row="5" FontSize="16" ItemsSource="{Binding Categories}">
   <TreeView.ItemTemplate>
      <HierarchicalDataTemplate DataType="{x:Type local:Category}" ItemsSource="{Binding NestedCategories}" >
         <TextBlock Text="{Binding CategoryName}" />
      </HierarchicalDataTemplate>
   </TreeView.ItemTemplate>
</TreeView>
<TreeView Name="TV_Categories" Grid.Row="5" FontSize="16" ItemsSource="{Binding Categories}">
 <TreeView.ItemTemplate>
      <HierarchicalDataTemplate ItemsSource="{Binding NestedCategories}" >
         <TextBlock Text="{Binding CategoryName}" />
      </HierarchicalDataTemplate>
   </TreeView.ItemTemplate>
</TreeView>

终于在这里找到了解决方案 -

应该这样做:

<TreeView Name="TV_Categories" Grid.Row="5" FontSize="16" ItemsSource="{Binding Categories}">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding NestedCategories}">
            <TextBlock Text="{Binding CategoryName}"/>
        </HierarchicalDataTemplate>
     </TreeView.ItemTemplate>
</TreeView>