0

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

dotNET
  • 33,414
  • 24
  • 162
  • 251
  • add another hierarchical template that contains the Categories, not sub-categories. This would be the root of your treeview. – deafjeff Nov 19 '14 at 09:00
  • @deafjeff: Thanks. Can you plz tell what would it look like? – dotNET Nov 19 '14 at 10:06
  • don't understand your problem, a screenshot would be helpful. Also you're using the `HierarchicalDataTemplate` in a wrong way, the root visual control inside it ***should be the header***, so usually we need some ContentControl in that place, not an ItemsControl like a `TreeViewItem` as you're currently using. – King King Nov 19 '14 at 10:16
  • each `Category` has a collection of sub-categories ***and*** also a collection of Accounts, so how exactly do you want to show 2 those collections under just a TreeViewItem? Looks like you have to merge them first. – King King Nov 19 '14 at 10:19
  • @KingKing: I added a screenshot of the current state of affairs. Plus a link to a codeproject article that addresses this issue. – dotNET Nov 19 '14 at 10:45
  • the hierarchical "category" template has to be a visual child of the Categories Root, to which the TreeView is bound. If you add a hierarchical template for the root, and shift your hierarchical Category-Type template inside it, it won't appear outside / on same level of the root, and it won't appear twice in your tree. – deafjeff Nov 19 '14 at 11:20
  • @deafjeff: Can you do me a favour and show me the template plz? I guess you have a point here but can't translate it into XAML on my own. – dotNET Nov 19 '14 at 11:30
  • correct, it's not trivial coding ..due to being busy will provide a solution in some hours as long as nobody has helped you out here :-) – deafjeff Nov 19 '14 at 11:48

1 Answers1

0

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

deafjeff
  • 754
  • 7
  • 25
  • Very elegant indeed. I later found [Josh Smith's excellent article on WPF TreeView binding](http://www.codeproject.com/Articles/26288/Simplifying-the-WPF-TreeView-by-Using-the-ViewMode) that I recommend to anyone who want to get up and going with WPF TreeView and MVVM. Even though that article is quite extensive, the basic idea is the same as you have elaborated. Thanks a bunch. – dotNET Nov 20 '14 at 09:03
  • btw I started out with that article too! :-) – deafjeff Nov 20 '14 at 09:05