Implement WPF treeview with different Parent Nodes a well as different child nodes?
Asked Answered
O

3

5

I want to implememt a tree view with has the following structure.....

[RootNode] <---- Root of tree
--[ParentNode P1] <---- Object of ModelClass P1
----[ChildNode C1] <----- Object of ModelClass C1 (have children of different type as well)
----[ChildNode C2] <----- Object of ModelClass C2 (have children of different type as well)
----[ChildNode C3] <----- Object of ModelClass C3 (have children of different type as well)
--[ParentNode Q1] <---- Object of ModelClass Q1
----[ChildNode B1] <----- Object of ModelClass B1 (have children of different type as well)
----[ChildNode B2] <----- Object of ModelClass B2 (have children of different type as well)
----[ChildNode B3] <----- Object of ModelClass B3 (have children of different type as well)
--[ParentNode R1] <---- Object of ModelClass R1
----[ChildNode A1] <----- Object of ModelClass A1 (have children of different type as well)
----[ChildNode A2] <----- Object of ModelClass A2 (have children of different type as well)
----[ChildNode A3] <----- Object of ModelClass A3 (have children of different type as well)

I have looked at many of the solution proposed on this site as well as on the web.....but just cant figure out how to do it.....

This is my first attempt on Wpf and this is a crucial requirement ......

Also finding hard to make the Object Model for the above different classes .....

All the classes displayed above have Other properties as well including their child nodes... i dont want to display all properties only the child nodes

Totally puzzled by ... seeing different solution

It would be really great if i could recieve some help in this regard...

Thanks

Orthochromatic answered 18/5, 2011 at 13:44 Comment(0)
O
9

Hierarchial Data Templates work if you use custom collections.... I made my classes like this :

public class EntityBase :ObservableCollection<object>
{

}

public class Parent : EntityBase
{

}  

public class ChildA : EntityBase // Dont make it a collection if it has noe childern to be displayed so dont inherit for EntityBase
{
   //Child Properties
}


public class ChildB : EntityBase
{
//Child Properties
}  

Now when you finally bind the data to you TreeView you'll make ChildA and ChildB items as Child items of Parent object i.e

    public ObservableCollection<object> GetData()
    {
         var temp = new ObservableCollection<object>();
         Parent parent = new Parent(); // Root Node
         temp.Add(parent);
         parent.Add(new ChildA()); // ChildA as Child1 of Parent

         parent.Add(new ChildA()); // ChildA as Child2 of Parent

         parent.Add(new ChildB()); // ChildB as Child3 of Parent

         parent.Add(new ChildB()); // ChildB as Child4 of Parent

         return temp;

    }  

Finally The Hierarchial data templates will look like..

<TreeView Name="test" Grid.Row="0" ItemsSource="{Binding Path=TreeData,Source={StaticResource ResourceKey=DataSource}}">
        <TreeView.Resources>
            <HierarchicalDataTemplate DataType="{x:Type EntityLayer:Parent}" ItemsSource="{Binding}">
                <StackPanel>
                    <TextBlock>Parent</TextBlock>
                </StackPanel>
            </HierarchicalDataTemplate>
            <HierarchicalDataTemplate DataType="{x:Type EntityLayer:ChildA}" ItemsSource="{Binding}">
                <StackPanel>
                    <TextBlock Text="{Binding Path = Name}"></TextBlock>
                </StackPanel>
            </HierarchicalDataTemplate>
            <HierarchicalDataTemplate DataType="{x:Type EntityLayer:ChildB}" ItemsSource="{Binding}">
                <StackPanel>
                    <TextBlock Text="{Binding Path = Name}"></TextBlock>
                </StackPanel>
            </HierarchicalDataTemplate>
        </TreeView.Resources>
    </TreeView>
Orthochromatic answered 10/6, 2011 at 11:43 Comment(0)
Z
2

It will be easier on you if any of these classes have base classes in common so that, for example, you can use one DataTemplate for several classes.

However, if each of these models really is different and don't have enough in common, you'll need to use a DataTemplateSelector, though built-in mechanisms may suffice.

Setup

Here's some of the foundation I made for the sake of recreating a similar situation. Each of the different classes inherits from List<object> so that it has a built-in way to contain children of any type, but I don't depend on that commonality for choosing datatemplates.

public class P1 : List<object> {
    public P1() {}
    public P1( IEnumerable<object> collection ) : base( collection ) {}
}

In addition, my root datasource is of type List<object> so that it can contain any types of objects.

The Window's constructor:

public MainWindow() {
    InitializeComponent();
    this.DataContext = MyDataSource.GetData(); // in which I construct the tree of parents and children
}

Solution

Start by making HierarchicalDataTemplates for each type. If any of the types do not contain children, you'll of course make DataTemplates instead for them:

<HierarchicalDataTemplate DataType="{x:Type loc:P1}"
                          ItemsSource="{Binding}">
    <TextBlock>a P1 object</TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:C1}"
                          ItemsSource="{Binding}">
    <TextBlock>a C1 object</TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:C2}"
                          ItemsSource="{Binding}">
    <TextBlock>a C2 object</TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:Q1}"
                          ItemsSource="{Binding}">
    <TextBlock>a Q1 object</TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:B1}"
                          ItemsSource="{Binding}">
    <TextBlock>a B1 object</TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:B2}"
                          ItemsSource="{Binding}">
    <TextBlock>a B2 object</TextBlock>
</HierarchicalDataTemplate>

Since each class descends from List<object>, the object itself is the source of children items, rather than a collection on a property, so I use ItemsSource="{Binding}". If the child items are in a collection under a property called Children, it would instead be ItemsSource="{Binding Children}", of course. This still allows each object to have its children in a different place.

The simplest way to implement your DataTemplateSelector in this example is to do nothing. Because I only specified the DataType and not an x:Key on the data templates, even though the collections are vague (List<object>) WPF will still examine the underlying type to determine if it is P1/Q1/etc. and find the correct HierarchicalDataTemplate to use. My TreeView only has to look like this:

<TreeView ItemsSource"{Binding}" />

However, let's say for the sake of showing you a DataTemplateSelector that you can't rely on it implicitly matching by Type. You'd put x:Keys on your datatemplates, such as this:

<HierarchicalDataTemplate DataType="{x:Type loc:P1}" x:Key="myKeyforP1"
                          ItemsSource="{Binding}">
    <TextBlock>a P1 object</TextBlock>
</HierarchicalDataTemplate>

Then your selector might internally use a Dictionary to determine what resource key to use for a given Type (keep in mind that this is a naive implementation):

public class CustomDataTemplateSelector : DataTemplateSelector {
    static Dictionary<Type, object> typeToKey = new Dictionary<Type, object>();
    static CustomDataTemplateSelector() {
        typeToKey[ typeof( P1 ) ] = "myKeyforP1";
    }

    public override DataTemplate SelectTemplate( object item, DependencyObject container ) {
        var element = container as FrameworkElement;
        if ( element != null && item != null ) {
            var itemtype = item.GetType();
            object keyObject;
            if ( typeToKey.TryGetValue( itemtype, out keyObject ) ) {
                var template = element.TryFindResource( keyObject ) as DataTemplate;
                if ( template != null ) {
                    return template;
                }
            }
        }
        return base.SelectTemplate( item, container );
    }
}

Then you'd add the selector to a resource dictionary and your TreeView would need another property assigned:

<Grid.Resources>
    <loc:CustomDataTemplateSelector x:Key="mySelector" />
</Grid.Resources>
<TreeView ItemsSource="{Binding}"
          ItemTemplateSelector="{StaticResource mySelector}"></TreeView>

And because base.SelectTemplate() will try to use the item's Type as a resource key, you can have a standard HierarchicalDataTemplate for the model and a customized version that will only be used if your TreeView is using the custom DataTemplateSelector:

<HierarchicalDataTemplate DataType="{x:Type loc:P1}"
                          ItemsSource="{Binding}">
    <TextBlock>a P1 object</TextBlock>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:P1}" x:Key="myKeyforP1"
                          ItemsSource="{Binding}">
    <TextBlock>a P1 that looks much different</TextBlock>
</HierarchicalDataTemplate>
Zephan answered 18/5, 2011 at 16:0 Comment(4)
Thanks for helping me ..... I was able to make a tree with the above proposed solution....though i didn't understood the concept of custom DataTemplate Selector...... continuing the above senario do i have to make a class for Root node ??? The tree i made does not have a root node its starting for Parent nodes... can this structure be serialised and deserialisedOrthochromatic
There are automatic ways that WPF tries to look up the correct DataTemplate, mostly based on the template's DataType being the same as the object it is trying to template. DataTemplateSelectors are for when you need a way to customize what template is chosen for displaying a particular type, especially when you have more than one possible template for a given type or want to switch them whenever you want. You could even create the template in the selector and return it.Zephan
It depends on whether the root node has any properties of its own or it's just used to be a parent of P1, Q1, R1, etc. If it's the latter case, there isn't much point to making a separate class. All I did was make a List with the top parent nodes and used that as the data source. As for serialization, that's a different problem. Usually you markup your classes with attributes that guide the serializer in how to serialize them.Zephan
Thanks i'll proceed with the following guidelinesOrthochromatic
F
0

Is it possible you can post further details of your solution. I'm trying to achieve the same thing, I've added multiple hierarchicaldatatemplates but would be interested in seeing the object model.

In my case I've got a Parent with the following properties

Public string Name { get; set; }
Public ObservableCollection<ChildA> ChildrenA { get; set; }
Public ObservableCollection<ChildB> ChildrenB { get; set; }

I want to show these in my treeview. Would be interested in knowing how I might structure the object model as I need something to display in the tree at the level above the individual ChildA and ChildB collections.

Flatter answered 8/6, 2011 at 12:59 Comment(3)
I posted my solution here you can check it out...suggest some improvements if you find .. it would be helpful..Orthochromatic
Is that what you are looking for.Orthochromatic
Wondering if you an also sort of achieve that if you had a ubiquitous interface IMyTreeViewItem as long as it also exposed INotifyPropertyChanged, for instance. The other trick is, where is HierarchicalDataTemplate positioned exactly? In the Resources? Was trying to make something like that work for Syncfusion SfTreeView for WPF, without much success. Which got me wondering about INPC etc.Fachan

© 2022 - 2024 — McMap. All rights reserved.