如何使用具有“无限”级别的模型中的mvvm填充WPF树视图

时间:2019-05-09 12:18:50

标签: c# wpf entity-framework treeview

我有一个实体框架模型,该模型具有自我内部的功能,用户可以根据需要在其中创建尽可能多的子类别。

public class Category
{
    public Category()
    {
        SubCategories = new ObservableCollection<Category>();
    }

    [Column("id")]
    public int Id { get; set; }

    [StringLength(100)]
    public string Name { get; set; }

    [Column("ParentID")]
    public int? ParentID { get; set; }


    [ForeignKey("ParentID")]
    public virtual ObservableCollection<Category> SubCategories { get; set; }
}

我当时正在考虑使用像这样的foreach来填充树视图:

Categories = new ObservableCollection<Category>(db.Categories.Where(x => x.ParentID == null));

foreach (var item in Categories)
{
    SubCategoriesModel = new ObservableCollection<Category>(db.Categories.Where(x => x.ParentID == item.Id));
    foreach (var subitem in SubCategoriesModel)
    {
        item.SubCategories.Add(subitem);
    }
}
<TreeView Grid.Row="0" ItemsSource="{Binding Categories}" MinWidth="220">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type data:Categories}" ItemsSource="{Binding SubCategories}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Id}" Margin="3 2" />
                <TextBlock Text=" - "/>
                <TextBlock Text="{Binding Name}" Margin="3 2" />
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.Resources>
    <e:Interaction.Behaviors>
        <behaviours:BindableSelectedItemBehavior SelectedItem="{Binding SelectedTreeCategory, Mode=TwoWay}" />
    </e:Interaction.Behaviors>
</TreeView>

我意识到这行不通。有更好的方法吗?

1 个答案:

答案 0 :(得分:1)

方法1

当您处理潜在数量无限的子级别时(例如,因为项目可以互相引用,并且在递归过程中会导致无限循环),我建议您在首次扩展项目时填充它们。

方法2

如果您没有递归并且想一次加载所有数据,则可以通过以递归方法加载它来简单地进行此操作(但请注意-如果级别太深,您可能会得到StackOverflowException)


示例方法1

针对这种情况的非常简单的视图模型如下所示:

public class Node
{
    public uint NodeId { get; set; }
    public string DisplayName { get; set; }
    public bool ChildrenLoaded { get; set; }

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

    public Node()
    {
        ChildrenLoaded = false;
        Children = new ObservableCollection<Node>();
    }

    public void LoadChildNodes()
    {
        if (ChildrenLoaded) return;

        // e.g. Every SubCategory with a parentId of this NodeId
        var newChildren = whereverYourDataComesFrom.LoadChildNodes(NodeId);

        Children.Clear();
        foreach (Node child in newChildren)
            Children.Add(child);

        ChildrenLoaded = true;
    }
}

像这样设置树形视图,其中Nodes是您首先加载的一个或多个根节点。 (在您的示例中,Categories的ParentId = null)

<TreeView TreeViewItem.Expanded="TreeViewItem_Expanded" ItemsSource="{Binding Nodes}">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
            <TextBlock Text="{Binding DisplayName}"/>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

TreeViewItem.Expanded事件是所谓的RoutedEvent btw。它不是由TreeView触发的,而是TreeViewItems本身,只是在可视树中冒泡(这是它的实际技术术语,还有 tunneling direct )。

每当节点第一次扩展时,您只需在TreeViewItem_Expanded事件处理程序中加载所有子节点。

private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
    Node node = ((FrameworkElement)e.OriginalSource).DataContext as Node;
    if (node == null) return;

    node.LoadChildNodes();
}

因此,无论您有多少项并且相互引用,您都只会加载根节点,而其他一切都按需进行。

将该原理转换为您的特定示例,我只是将数据的加载方法拆分为根Category条目,然后将SubCategories加载到Expanded事件处理程序中,而不是预先加载所有内容

由于您的大多数代码已经几乎相同,因此我认为这应该是相当容易的修改。


示例方法2

private void LoadRootCategories()
{
    Categories = new ObservableCollection<Category>(db.Categories.Where(x => x.ParentID == null));

    foreach (var item in Categories)
    {
        LoadSubCategories(item)
    }
}

private void LoadSubCategories(Category item)
{
    item.SubCategories = new ObservableCollection<Category>(db.Categories.Where(x => x.ParentID == item.Id));

    foreach (var subitem in item.SubCategories)
    {
        // Recursive call
        LoadSubCategories(subitem);
    }
}