我有一个实体框架模型,该模型具有自我内部的功能,用户可以根据需要在其中创建尽可能多的子类别。
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>
我意识到这行不通。有更好的方法吗?
答案 0 :(得分:1)
方法1
当您处理潜在数量无限的子级别时(例如,因为项目可以互相引用,并且在递归过程中会导致无限循环),我建议您在首次扩展项目时填充它们。
方法2
如果您没有递归并且想一次加载所有数据,则可以通过以递归方法加载它来简单地进行此操作(但请注意-如果级别太深,您可能会得到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;
}
}
像这样设置树形视图,其中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事件处理程序中,而不是预先加载所有内容
由于您的大多数代码已经几乎相同,因此我认为这应该是相当容易的修改。
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);
}
}