Search code examples
c#wpfxamlmvvmtreeview

Simple Nested TreeView Xaml structure?


I am trying to build a WPF TreeView with three layers. CountryReportTitle is a string property and ArticleCategoryTitlesList is a collection, both exposed from my ViewModel. There is no class hierarchy defined. This is the structure I'm looking for:

enter image description here

This is my attempted Xaml but I'm getting an exception in the Xaml at runtime:

{"Item has already been added. Key in dictionary: 'DataTemplateKey(ISESApp.ViewModels.ReportViewModel)'  Key being added: 'DataTemplateKey(ISESApp.ViewModels.ReportViewModel)'"}

Xaml:

       <TreeView ItemsSource="{Binding CountryReportTitle}">
                    <TreeView ItemsSource="{Binding CountryReportTitle}">
                        <TreeView.Resources>
                            <HierarchicalDataTemplate  DataType="{x:Type local:ReportViewModel}"
                                ItemsSource="{Binding ArticleCategoryTitlesList}">
                                <TextBlock Text="{Binding CategoryTitle}" />
                            </HierarchicalDataTemplate>
                            <HierarchicalDataTemplate  DataType="{x:Type local:ReportViewModel}"
                                ItemsSource="{Binding ArticleCatagoryTypesList}">
                                <TextBlock Text="{Binding ArticleTitle}" />
                            </HierarchicalDataTemplate>
                            <DataTemplate  DataType="{x:Type local:ReportViewModel}">
                                <TextBlock Text="{Binding ArticleTitle}" />
                            </DataTemplate>
                        </TreeView.Resources>
                    </TreeView>
                </TreeView>

Local: is a namespace to my ViewModel:

xmlns:local="clr-namespace:MyApp.ViewModels"

What am I doing wrong, what is the best approach for this problem?


Solution

  • Here's my go-to example for treeviews.

    Use a HierarchicalDataTemplate for elements in the tree. Note that there are three layers, and each layer is its own type. This is for convenience, but you could define one type and use one template or any mix of types for your tree. Having different types represent different things in the tree makes using templates extremely convenient.

    The data classes

    public class ViewModel
    {
        public ObservableCollection<ItemA> ItemsA { get; set; }
        public ViewModel()
        {
            ItemsA = new ObservableCollection<ItemA>(new[]{
                new ItemA{Name = "A one"},
                new ItemA{Name = "A Two"},
                new ItemA{Name = "A Three"},
            });
        }
    }
    
    public class ItemA
    {
        public ObservableCollection<ItemB> ItemsB { get; set; }
        public string Name { get; set; }
        public ItemA()
        {
            ItemsB = new ObservableCollection<ItemB>(new[]{
                new ItemB{Name = "B one"},
                new ItemB{Name = "B Two"},
                new ItemB{Name = "B Three"},
            });
        }
    }
    public class ItemB
    {
        public ObservableCollection<ItemC> ItemsC { get; set; }
        public string Name { get; set; }
        public ItemB()
        {
            ItemsC = new ObservableCollection<ItemC>(new[]{
                new ItemC{Name = "C one"},
                new ItemC{Name = "C Two"},
                new ItemC{Name = "C Three"},
            });
        }
    }
    public class ItemC
    {
        public string Name { get; set; }
    }
    

    And the UI

     <TreeView ItemsSource="{Binding ItemsA}">
        <TreeView.Resources>
            <HierarchicalDataTemplate  DataType="{x:Type t:ItemA}"
                                        ItemsSource="{Binding ItemsB}">
                <TextBlock Text="{Binding Name}" />
            </HierarchicalDataTemplate>
            <HierarchicalDataTemplate  DataType="{x:Type t:ItemB}"
                                        ItemsSource="{Binding ItemsC}">
                <TextBlock Text="{Binding Name}" />
            </HierarchicalDataTemplate>
            <DataTemplate  DataType="{x:Type t:ItemC}">
                <TextBlock Text="{Binding Name}" />
            </DataTemplate>
        </TreeView.Resources>
    </TreeView>
    

    gives you a simple treeview

    enter image description here