1

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?

Hardgraf
  • 2,566
  • 4
  • 44
  • 77

2 Answers2

4

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

ahmed shawkey
  • 19
  • 1
  • 6
  • Thanks. Both my collections are in the same viewModel. I don't really want to separate into different classes. If so how would I define DataType? – Hardgraf Mar 11 '15 at 18:06
  • @Hardgraf Let's say ItemsB is on the ViewModel and not in ItemA. You'd have to bind back to the ViewModel in the HierarchicalDataTemplate for ItemA-- `ItemsSource="{Binding DataContext.ItemsB, ElementName=root}"` making sure to give your Window an `x:Name="root"` so that ElementName points to the right location. –  Mar 11 '15 at 18:09
  • think I'm nearly there. Changed the question to my new Xaml but still unsure on how to reference the correct DataTypes for each of my HierarchicalDataTemplates. – Hardgraf Mar 12 '15 at 11:07
  • @Hardgraf I don't know. It's unclear from your question what is going on. You say things are wrong, yet you include no information about how it's going wrong, if there are errors, etc. –  Mar 12 '15 at 13:51
  • Fair point. My current code is in the question. The issue is that I don't know until run time what each of the lists is going to contain so the TreeView needs to be built dynamically at run time. I think my issue is where I'm setting the DataType- DataType="{x:Type local:ReportViewModel}" I don't think this is correct but I'm unsure of how to bind each HierarchicalDataTemplate to the correct list in the viewModel – Hardgraf Mar 12 '15 at 13:53
  • @Hardgraf As my answer points out, the nodes in the model tree have types based on what they are. The DataTemplates are designed specifically for each type, and are keyed by the type itself ({x:Type t:Model}). When the TreeView encounters a model of type X and must create a visual for that type, it searches for the appropriate template and uses it. I'm not sure where your confusion lies. I've given you a complete application--go create a new wpf application, paste my code in there, and observe how it works. Play around with it. Change this and that. It should clarify things. –  Mar 12 '15 at 13:57
  • Yes, I don't know how to point the DataTemplate type to the correct Type. I'm getting the xaml exception: {"Item has already been added. Key in dictionary: 'DataTemplateKey(ISESApp.ViewModels.ReportViewModel)' Key being added: 'DataTemplateKey(ISESApp.ViewModels.ReportViewModel)'"} because the namespace is wrong but I don't know how to resolve the namespace to point to the type to expect from the viewmodel. I've played with your example but can't resolve DataType="{x:Type t:ItemB}" – Hardgraf Mar 12 '15 at 14:09
  • @Hardgraf If your view is in assembly X, and your ViewModel is in assembly X, and your ViewModel's namespace is `namespace xyz.abc` then the xmlns would be "clr-namespace:xyz.abc". If it is in assembly Y and not in assembly X, it would be "clr-namespace:xyz.abc;assembly=X" You need to back up and figure out how to define xaml namespaces if this is giving you angina. Take it a step at a time. –  Mar 12 '15 at 14:22
  • Fantastic @Will, thanks for all your patience with me. This is a great solution. I just replaced the hard coded values with a foreach over my lists to assign the values for each ObservableCollection. – Hardgraf Mar 12 '15 at 15:19
3

You need to bind the TreeView Resources inside your TreeView:

<TreeView.Resources>
    <HierarchicalDataTemplate
        DataType="{x:Type local:FirstLayer}"
        ItemsSource="{Binding Children}">
        <StackPanel Orientation="Horizontal" Margin="2">
            <TextBlock Text="{Binding ChildrenName}" />
        </StackPanel>
    </HierarchicalDataTemplate>
    <HierarchicalDataTemplate
        DataType="{x:Type local:SecondLayer}"
        ItemsSource="{Binding Children}">
        <StackPanel Orientation="Horizontal" Margin="2">
            <TextBlock Text="{Binding ChildrenName}" />
        </StackPanel>
    </HierarchicalDataTemplate>
</TreeView.Resources>

I think you'll need to modify your project to bind your Treeview with your ViewModel instead of a list of string

Here is a good Example

C1rdec
  • 1,647
  • 1
  • 21
  • 46