65

I am trying to get a TreeView to bind my collection so that all groups show nested groups and each group will show entry.

How can I use the HierarchicalDataTemplate so that the TreeView will process both SubGroups and Entries collection?

Groups show subgroups and entries:

Example:
Group1
--Entry
--Entry
Group2
--Group4
----Group1
------Entry
------Entry
----Entry
----Entry
--Entry
--Entry
Group3
--Entry
--Entry


Objects:


namespace TaskManager.Domain
{
    public class Entry
    {
        public int Key { get; set; }
        public string Name { get; set; }
    }
}

namespace TaskManager.Domain
{
    public class Group
    {
        public int Key { get; set; }
        public string Name { get; set; }

        public IList<Group> SubGroups { get; set; }
        public IList<Entry> Entries { get; set; }
    }
}

Test data:


namespace DrillDownView
{
    public class TestData
    {

        public IList<Group> Groups = new List<Group>();

        public void Load()
        {
            Group grp1 = new Group() { Key = 1, Name = "Group 1", SubGroups = new List<Group>(), Entries = new List<Entry>() };
            Group grp2 = new Group() { Key = 2, Name = "Group 2", SubGroups = new List<Group>(), Entries = new List<Entry>() };
            Group grp3 = new Group() { Key = 3, Name = "Group 3", SubGroups = new List<Group>(), Entries = new List<Entry>() };
            Group grp4 = new Group() { Key = 4, Name = "Group 4", SubGroups = new List<Group>(), Entries = new List<Entry>() };

            //grp1
            grp1.Entries.Add(new Entry() { Key=1, Name="Entry number 1" });
            grp1.Entries.Add(new Entry() { Key=2, Name="Entry number 2" });
            grp1.Entries.Add(new Entry() { Key=3,Name="Entry number 3" });

            //grp2
            grp2.Entries.Add(new Entry(){ Key=4, Name = "Entry number 4"});
            grp2.Entries.Add(new Entry(){ Key=5, Name = "Entry number 5"});
            grp2.Entries.Add(new Entry(){ Key=6, Name = "Entry number 6"});

            //grp3
            grp3.Entries.Add(new Entry(){ Key=7, Name = "Entry number 7"});
            grp3.Entries.Add(new Entry(){ Key=8, Name = "Entry number 8"});
            grp3.Entries.Add(new Entry(){ Key=9, Name = "Entry number 9"});

            //grp4
            grp4.Entries.Add(new Entry(){ Key=10, Name = "Entry number 10"});
            grp4.Entries.Add(new Entry(){ Key=11, Name = "Entry number 11"});
            grp4.Entries.Add(new Entry(){ Key=12, Name = "Entry number 12"});

            grp4.SubGroups.Add(grp1);
            grp2.SubGroups.Add(grp4);

            Groups.Add(grp1);
            Groups.Add(grp2);
            Groups.Add(grp3);
        }
    }
}

XAML:


<Window x:Class="DrillDownView.Window2"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TaskManager.Domain;assembly=TaskManager.Domain"
        Title="Window2" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <TreeView Name="GroupView" Grid.Row="0" Grid.Column="0" ItemsSource="{Binding}">
            <TreeView.Resources>
                <HierarchicalDataTemplate DataType="{x:Type local:Group}" ItemsSource="{Binding SubGroups}">
                    <TextBlock Text="{Binding Path=Name}" />
                </HierarchicalDataTemplate>
                <HierarchicalDataTemplate DataType="{x:Type local:Entry}" ItemsSource="{Binding Entries}">
                    <TextBlock Text="{Binding Path=Name}" />
                </HierarchicalDataTemplate>
            </TreeView.Resources>
        </TreeView>
    </Grid>
</Window>

XAML.CS:


public partial class Window2 : Window
{
    public Window2()
    {
        InitializeComponent();
        LoadView();
    }

    private void LoadView()
    {
        TestData data = new TestData();
        data.Load();
        GroupView.ItemsSource = data.Groups;
    }
}
lokusking
  • 7,396
  • 13
  • 38
  • 57
longday
  • 4,075
  • 4
  • 28
  • 35
  • Very looong post and is missing the question that you probably wanted to ask. – Gishu Dec 16 '09 at 05:25
  • How can I use the HierarchicalDataTemplate so that treeview will process both SubGroups and Entries collection? – longday Dec 16 '09 at 05:30
  • 3
    reformatted and moved some code around. Revert if you don't like it. Working on your question.. – Gishu Dec 16 '09 at 05:35
  • Is this a mistake? Setting the tree items source once in YAML and once in code. the code I am expecting to raise a exception. GroupView.ItemsSource = data.Groups – profimedica May 16 '19 at 23:57

4 Answers4

123

A HierarchicalDataTemplate is a way of saying 'this is how you render this type of object and here is a property that can be probed to find the child nodes under this object'

Therefore you need a single property that returns the 'children' of this node. e.g. (If you can't make both Group and Entry derive from a common Node type)

public class Group{ ....
        public IList<object> Items
        {
            get
            {
                IList<object> childNodes = new List<object>();
                foreach (var group in this.SubGroups)
                    childNodes.Add(group);
                foreach (var entry in this.Entries)
                    childNodes.Add(entry);

                return childNodes;
            }
        }

Next you don't need a HierarchicalDataTemplate for entry since an entry doesn't have children. So the XAML needs to be changed to use the new Items property and a DataTemplate for Entry:

<TreeView Name="GroupView" Grid.Row="0" Grid.Column="0" ItemsSource="{Binding}">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type local:Group}" ItemsSource="{Binding Items}">
            <TextBlock Text="{Binding Path=Name}" />
        </HierarchicalDataTemplate>
        <DataTemplate DataType="{x:Type local:Entry}" >
            <TextBlock Text="{Binding Path=Name}" />
        </DataTemplate>
    </TreeView.Resources>
</TreeView>

And here's what that looks like. Screenshot of Output

Soleil
  • 6,404
  • 5
  • 41
  • 61
Gishu
  • 134,492
  • 47
  • 225
  • 308
  • +1 from me - because you validated my own answer on both points ;) – kiwipom Dec 16 '09 at 06:21
  • @Gishu: What do you think of my alternative implementation (see my answer)? I prefer to use a return type of `IEnumerable` except when I really use an `IList`. Additionally, this method allows us to utilize the `yield` keyword to simplify the code. – Matthew Aug 04 '11 at 14:38
  • 2
    @Matthew.. nice improvement. The best code is the code that you don't have to write :) +1 – Gishu Aug 04 '11 at 15:32
  • That answer saved me ton of time. Thank you very much Gishu for the help – Shaik Dec 02 '11 at 20:06
  • @Gishu, if you have the time, would you mind taking a look at my question here: [HierarchicalDataTemplate and Many-to-Many with Payload Binding Issue](http://stackoverflow.com/q/16095496/1751090) ? I would appreciate it very much--seem to have a very good handle on the concepts involved. Thank you for reading :) – Rachael Apr 19 '13 at 17:06
  • @Gishu Any idea about the implementing same in a Windows phone 8 application.here is question http://stackoverflow.com/questions/23908773/wp8-control-that-supports-hierarchicaldatatemplate-binding – Sebastian May 28 '14 at 10:54
  • Thanks for the great example! The only thing I'd suggest is to use an interface instead of object. – Taras Aug 25 '17 at 17:14
  • Instead of adding into List it's better to use CompositeCollection – bitman Oct 19 '19 at 18:05
  • Not only helped my problem but taught me the reasons why. Thank you Gishu. I was struggling understanding the connection. – Holden Aug 03 '20 at 17:38
12

Here is an alternative implementation of Gishu's answer that returns an IEnumerable rather than an IList, and makes use of the yield keyword to simplify the code:

public class Group
{
    ...

    public IEnumerable<object> Items
    {
        get
        {
            foreach (var group in this.SubGroups)
                yield return group;
            foreach (var entry in this.Entries)
                yield return entry;
        }
    }
}
Matthew
  • 28,056
  • 26
  • 104
  • 170
  • 1
    Thank you! I was getting stuck and this helped so much. I wasn't able to get both entries and groups being displayed correctly at the same time. This saved me a lot of time. – user1799563 Jun 02 '14 at 12:57
11

I think you are most of the way there... with a tiny bit of rework you should get this working fairly easily...

I would suggest you create a base abstract class (or an interface, whichever you prefer) and inherit/implement it for both the Group and Entry class...

That way, you can expose a property within your Group object

public ObservableCollection<ITreeViewItem> Children { get; set; }

^at this point, you can make a decision if this replaces your lists of SubGroups and Entries, or merely appends them together and returns them in the property getter...

Now all you need is to populate the Children collection with either Group or Entry objects, and the HierarchicalDataTemplate will render correctly when the objects are placed in the TreeView..

One final thought, if Entry is always the 'bottom level' of the tree (ie has no children) then you do not need to define a HierarchicalDataTemplate for the Entry object, a DataTemplate will suffice.

Hope this helps :)

kiwipom
  • 7,639
  • 37
  • 37
  • 1
    A basic working example would help the newbies grasp the concept. But regardless excellent thought. – ΩmegaMan Jun 22 '15 at 18:25
  • I took your advice and loaded up generic JSON using the `ITreeViewItem` with raw JSON as input. My answer can be seen here [How to display JSON in WPF TreeView](https://stackoverflow.com/a/59431426/285795) – ΩmegaMan Dec 20 '19 at 21:20
3

This post helped me out when looking for a solution for the same issue: https://www.pmunin.com/2012/02/xaml-binding-to-compositecollection.html

using MultiBinding and CompositeCollectionConverter

Michael Phillips
  • 862
  • 2
  • 7
  • 17
Anders
  • 177
  • 2
  • 7
  • 7
    Whilst this may theoretically answer the question, [it would be preferable](http://meta.stackexchange.com/q/8259) to include the essential parts of the answer here, and provide the link for reference. – Conrad Frix Apr 08 '13 at 15:20