How to show nested relationship in TreeView

1k views Asked by At

Struggling with showing nested relationship in my TreeView. Here is the scenario:

In the database I have Category and Account tables. Each category can have zero or more sub-categories so this table has a nested relationship with itself. Each category/sub-category can have zero or more Account in it, there is a one-to-many relation between Category and Account. Simple, isn't it!

On top of my DB, I have EDMX, with Categories and Accounts entities in it and their associations as I mentioned above. For ease of understanding, I have renamed navigation properties so that Categories now has ParentCategory, ChildCategories and Accounts properties in it.

On top of EDMX, I have my ViewModel, which defines a public property named AllCategories. My TreeView will bind to this property. I initialize this property at the startup like this:

using (MyEntities context = new MyEntities())
    Categories = context.Categories.Include(x => x.Accounts).ToList();

Finally I use the following HierarchicalDataTemplate to show this stuff:

 <HierarchicalDataTemplate DataType = "{x:Type local:Category}" ItemsSource = "{Binding Path=ChildCategories}">
  <TreeViewItem Header="{Binding Name}" ItemsSource="{Binding Accounts}" />
</HierarchicalDataTemplate>

<DataTemplate DataType = "{x:Type local:Account}">
  <TextBlock Text="{Binding Name}" />
</DataTemplate>

This runs fine and shows categories, sub-categories and accounts in the tree, but the problem is that sub-categories show up not only under their parent category, but also at the root-level. This happens for categories of all depths. What am I doing wrong here?

Note: If I add .Where(x=>!x.ParentID.HasValue) in the VM, it shows only the root category and its immediate children, nothing else.

Edit

Here's what it currently looks like. Everything goes fine up to the dotted white line (I added that line manually for illustration; has nothing to do with WPF). After that, the sub-categories start repeating with their child sub-categories. This process continues over and over till the leaf sub-categories. I believe I understand what's going on here, but don't have a solution for it. For reference, this guy presents a solution of the problem, but he is using DataSets, whereas I'm working with EF and can't translate his solution into my scenario.

enter image description here

1

There are 1 answers

2
deafjeff On BEST ANSWER

The idea is to connect your business data by ObservableCollections and leave your Hierarchical templates simple, so that the treeview won't show duplicate entries. The sample code shows nested viewmodel relationship and the corresponding hierarchical templates. For simplification, the Root is an ObservableCollection (otherwise you would need to add INotifyPropertyChanged here and selective ItemsSource Binding in the TreeView)

<Window x:Class="MyWpf.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MyWpf"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <HierarchicalDataTemplate DataType = "{x:Type local:RootItem}" ItemsSource = "{Binding Path=Categories}">
        <TextBlock Text="{Binding Header}"></TextBlock>
    </HierarchicalDataTemplate>
    <HierarchicalDataTemplate DataType = "{x:Type local:CatNode}" ItemsSource = "{Binding Path=Items}">
        <TextBlock Text="{Binding Header}"></TextBlock>
    </HierarchicalDataTemplate>
</Window.Resources>
<Grid>
    <TreeView ItemsSource="{Binding MyRoot}"/>
</Grid>
</Window>

namespace MyWpf
{
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
        MyRoot = new ObservableCollection<RootItem>();
        MyRoot.Add(new RootItem());
    }
    public ObservableCollection<RootItem> MyRoot { get; set; }
}

public class RootItem
{
    public RootItem()
    {
        Categories = new ObservableCollection<CatNode>();
        Categories.Add(new CatNode { Header = "Cat1" });
        Categories[0].Items.Add("Item11");
        Categories[0].Items.Add("Item12");
        Categories.Add(new CatNode { Header = "Cat2" });
        Categories[1].Items.Add("Item21");
        Categories[1].Items.Add("Item22");
    }
    public string Header { get { return "Root"; }}
    public ObservableCollection<CatNode> Categories { get; set; }
}
public class CatNode
{
    public CatNode()
    {
        Items = new ObservableCollection<string>();
    }
    public string Header { get; set; }
    public ObservableCollection<string> Items { get; set; }
}
}

screenshot of sample app