如何使用 "infinte" 级别模型中的 mvvm 填充 wpf 树视图
How to fill a wpf tree view using mvvm from a model with "infinte" levels
我有那个 entity framework 内部模型,用户可以根据需要将子类别分成多个子类别。
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>
我意识到这行不通。有更好的方法吗?
方法一
当您处理可能无限数量的 sub-levels 时(例如,因为项目可以相互引用并会在递归期间导致无限循环),我建议在项目第一次出现时填充它们展开。
方法二
如果您没有递归并且想一次加载所有数据,您可以简单地通过以递归方法加载它来实现(不过要小心 - 如果级别太深,您可能会得到 WhosebugException)
示例方法 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
event is a so called RoutedEvent 顺便说一句。它不是由 TreeView 触发的,而是由 TreeViewItems 本身触发的,只是 bubbles 你的视觉树(这是它的实际技术术语,还有 tunneling 和 直接 ).
每当第一次展开节点时,您只需在 TreeViewItem_Expanded
事件处理程序中加载所有子节点。
private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
Node node = ((FrameworkElement)e.OriginalSource).DataContext as Node;
if (node == null) return;
node.LoadChildNodes();
}
所以不管你有多少项目,如果相互引用,你只加载根节点,其他一切都会发生on-demand。
将该原则转化为您的具体示例,我只是将数据的加载方法拆分为根 Category
条目,并在扩展事件处理程序中加载 SubCategories
而不是 pre-loading一切。
由于你的大部分代码已经几乎相同,我认为这应该是一个相当容易的修改。
示例方法 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);
}
}
我有那个 entity framework 内部模型,用户可以根据需要将子类别分成多个子类别。
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>
我意识到这行不通。有更好的方法吗?
方法一
当您处理可能无限数量的 sub-levels 时(例如,因为项目可以相互引用并会在递归期间导致无限循环),我建议在项目第一次出现时填充它们展开。
方法二
如果您没有递归并且想一次加载所有数据,您可以简单地通过以递归方法加载它来实现(不过要小心 - 如果级别太深,您可能会得到 WhosebugException)
示例方法 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
event is a so called RoutedEvent 顺便说一句。它不是由 TreeView 触发的,而是由 TreeViewItems 本身触发的,只是 bubbles 你的视觉树(这是它的实际技术术语,还有 tunneling 和 直接 ).
每当第一次展开节点时,您只需在 TreeViewItem_Expanded
事件处理程序中加载所有子节点。
private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
Node node = ((FrameworkElement)e.OriginalSource).DataContext as Node;
if (node == null) return;
node.LoadChildNodes();
}
所以不管你有多少项目,如果相互引用,你只加载根节点,其他一切都会发生on-demand。
将该原则转化为您的具体示例,我只是将数据的加载方法拆分为根 Category
条目,并在扩展事件处理程序中加载 SubCategories
而不是 pre-loading一切。
由于你的大部分代码已经几乎相同,我认为这应该是一个相当容易的修改。
示例方法 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);
}
}