Question

I understand there are a plenty of examples of using a treeview in wpf. I know it is hard to believe but I think I have a legitimate question not yet addressed by the myriad of questions regarding treeviews already on StackOverflow.

(TL;DR version: Tried a lot of stuff. Looked at plenty of websites. Have Complex Tree Structure. Looking for a simple way of achieving desired tree results. Question at the bottom.)

So here goes...

I have read Josh's famous post on TreeViews

Of course, Philipp Sumi has his own very useful post on using TreeViews.

The main point of the desired output is that items of different types are presented under their respective headers, and that Objects can be represented twice, in different locations. Suggesting that a DataTemplateSelector alone will not provide me with my desired TreeView.

Desired Output:

Root  
 |--Header A (People)
      |---Person A1 (Jane)
      |---Person A2 (Steve)
 |--Header B (Cars)
      |---Car B1 (Red Car)
      |---Car B2 (Blue Car)
            |---Person A1 (Jane)
            |---Person A3 (Jon)

Note: A3 is Jon Skeet the Chuck Noris of SO

The first part of my approach is to essentially use a CompositeCollection though I am not actually using it. Nevertheless this post seems to suggest it isn't going to solve my problem. As People, ie. Steve, doesn't have other people, cars, or any other object contained within his data, so he is an end item.

View Model:

What I am binding to is this:

public ObservableCollection<object> Children
    {
        get
        {
            ObservableCollection<object> childNodes = new ObservableCollection<object>();
            foreach (var person in PeopleCollection)
                childNodes.Add(person);
            foreach (var car in CarCollection)
                childNodes.Add(car);
            return childNodes;
        }
    }

Template Selector:

We have to manage our multiple types of objects in the TreeView so I am using a DataTemplateSelector, as so:

class MyTemplateSelector : DataTemplateSelector
{
    public HierarchicalDataTemplate CarTemplate { get; set; }
    public HierarchicalDataTemplate PeopleTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item.GetType() == typeof(Car))
            return CarTemplate;
        if (item.GetType() == typeof(Person))
            return (PeopleTemplate);
        return (null);
    }
}

XAML:

Lastly, there is some XAML to bundle this all together nicely.

    <UserControl.Resources>
    <HierarchicalDataTemplate x:Key="m_CarTemplate"
                              DataType="{x:Type local:Car}"
                              ItemsSource="{Binding People}" >
        <TextBlock Text="{Binding CarColor}" />
    </HierarchicalDataTemplate>

    <HierarchicalDataTemplate x:Key="m_PeopleTemplate"
                              DataType="{x:Type local:Person}">
        <TextBlock Text="{Binding PersonName}" />
    </HierarchicalDataTemplate>

    <local:ProjectTemplateSelector
        CarTemplate="{StaticResource m_CarTemplate}"
        PeopleTemplate ="{StaticResource m_PeopleTemplate}"
        x:Key="myTemplateSelector"/>
      </UserControl.Resources>

A bit further down in the XAML:

    <TreeView Width="Auto" Height="Auto" HorizontalAlignment="Left"
                  VerticalAlignment="Top" Margin="0,0,0,0" Name="m_TreeView"
                  ItemsSource="{Binding ViewModel.Children}"
                  ItemTemplateSelector="{StaticResource myTemplateSelector}"
                  />

Potential Solutions:

The major set back to Philipp's approach is that I really just want to set Categories at the first level, ideally through <TreeViewItem Header="Cars" /> but that seems like it will fail. Next, I don't have items for these Category level items, ie. Cars and People, which are just strings. I suppose I could make these place-holder objects, and give them their respective collections than make properties out of them on my viewModel, but that seems slightly excessive just to get some headers... Particularly because already exists.

A second option would be the CompositeCollection approach, with a good example but I'm not entirely sure it will solve my problem. For one, I've not seen this used in a TreeView so I'm not sure if it would be appropriately applied there, and I want to use TreeView to preserve the hierarchical nature of the data.

Lastly, I attempted my own variation where in the HierarchicalDataTemplate I add a <TreeViewItem Header="Cars" /> but this doesn't work, as every Car gets its own header.

The Question

Main Question: What is the appropriate method, through binding, to achieve the desired structure of the TreeView? Thus producing a result where all cars are presented under the Cars header, People under the People Header, with the exception that people, who are in a Cars.PeopleCollection are also displayed as children of the respective Car object.

Secondary Question: Is a common, and appropriate approach, to make a Wrapper class specifically for the TreeViewItems? Is that what I should be doing? As I described in Philipp's Potential Solution.

I should also mention that given the solution as I have described here, the code compiles. The issue is that there are no headers. The next issue, is after I add people to the cars, and then attempt to expand the car, I receive the following error: Must disconnect specified child from current parent Visual before attaching to new parent Visual. Which I assume is a product of this line of code: ObservableCollection<object> childNodes = new ObservableCollection<object>(); but frankly its late and I am tired so I can't conjure up the appropriate way to do this, perhaps CompositeCollection is what I need.

Était-ce utile?

La solution

To add a category header, I see two possible solutions.

The first one would be to create an object "Category" and add it to your children collection. Then, you create the dataTemplate that goes with it.

Another solution would be to:

1- Create a class that gather a category name and a collection of car or people

2- Create a dataTemplate for this class (a textblock to display the category name and a treeview to display the collection with the appropriate dataTemplate)

3- Your children list would be a list of this object.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top