【问题标题】:How to fill a wpf tree view using mvvm from a model with "infinte" levels如何使用 mvvm 从具有“无限”级别的模型填充 wpf 树视图
【发布时间】:2019-05-09 12:18:49
【问题描述】:

我有那个内部的实体框架模型,用户可以根据需要将子类别制作成子类别。

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>

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

【问题讨论】:

  • @dymanoid 对此感到抱歉,这是一个错字,它会获取数据但只有一个级别,除非我想永远重复 foreach 语句
  • 我看到的唯一选择是在项目展开时加载它,而不是在那之前。

标签: c# wpf entity-framework treeview


【解决方案1】:

方法一

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

方法二

如果您没有递归并且想要一次加载所有数据,您可以简单地通过以递归方法加载它来完成(但要小心 - 如果级别太深,您可能会收到 StackOverflowException)


示例方法一

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

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;
    }
}

像这样设置 Treeview,其中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 顺便说一句。它不是由 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 条目,然后在 Expanded 事件处理程序中加载 SubCategories,而不是预先加载所有内容。

由于您的大部分代码已经几乎相同,我认为这应该是一个相当容易的修改。


示例方法二

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);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-10
    • 2013-09-21
    • 1970-01-01
    相关资源
    最近更新 更多