views:

1128

answers:

1

I have a TreeView that creates all its items from databound ObservableCollections. I have a hierarchy of GameNode objects, each object has two ObservableCollections. One collections has EntityAttrib objects and the other have GameNode objects. You could say that the GameNode object represents folders and EntityAttrib represents files. To display both attrib and GameNodes in the same TreeView I use Multibinding. This all works fine in startup, but when I add a new GameNode somewhere in the hierarchy the TreeView is not updated. I set a breakpoint in my converter method but it's not called when adding a new GameNode. It seems that the ObservableCollection is not notifying the MultiBinding of the change. If I comment out the MultiBinding and only bind the GameNode collection it works as expected.

XAML:

<HierarchicalDataTemplate DataType="{x:Type local:GameNode}">
        <HierarchicalDataTemplate.ItemsSource>
            <MultiBinding Converter="{StaticResource combineConverter}">
                <Binding Path="Attributes" />
                <Binding Path="ChildNodes" />
            </MultiBinding>
        </HierarchicalDataTemplate.ItemsSource>
        <TextBlock Text="{Binding Path=Name}" ContextMenu="{StaticResource EntityCtxMenu}"/>
    </HierarchicalDataTemplate>

C#:

public class GameNode
{
    string mName;
    public string Name { get { return mName; } set { mName = value; } }

    GameNodeList mChildNodes = new GameNodeList();
    public GameNodeList ChildNodes { get { return mChildNodes; } set { mChildNodes = value; } }

    ObservableCollection<EntityAttrib> mAttributes = new ObservableCollection<EntityAttrib>();
    public ObservableCollection<EntityAttrib> Attributes { get { return mAttributes; } set { mAttributes = value; } }
}

GameNodeList is a subclassed ObservableCollection

A: 

The best way to do (only if your EntityAttrib and GameNode are two different classes inherited from same base class) would be actually defining two data templates as below.

<HierarchicalDataTemplate DataType="{x:Type local:GameNode}">        

<HierarchicalDataTemplate DataType="{x:Type local:EntityAttrib}">

This is better because its easier to recollect later on. Consider file system objects, both FileInfo and DirectoryInfo classes are actually derived from FileSystemInfo class which shares common property.

You should have a baseclass, "BaseGameNode" , have something in it, "GameNode" and "GameEntityAttribNode" both derived from "BaseGameNode". And they should have only one Children property that is observable collection of type BaseGameNode but its item instance should be different as needed.

You can define multiple templates provided they have something to distinguish, the type will be automatically chosen for the children of the node.

Value you have bound will not refresh because they are not dependency property neither they notify on change. Since multibinding will not detect collection change event at all unless the reference/instance of collection changes. When you change an item in collection your actual instance of attributes/properties remains same.

When you bind ItemsSource = collection, its the ItemsSource that will listen for CollectionChange event and update the items accordingly.

Akash Kava
I don't understand how this helps. I will still have the same problem with multibinding.I actually do have a HierarchicalDataTemplate for the EntityAttrib type to display them in a different color.
Decept
Your converter will not get called 2nd time at all, because it will only be called when the node is created. Because your multibinding will not detect change inside the observable collection, but its the ItemsSource that will detect collection change and refresh itself.
Akash Kava
Maybe I'm stupid, but I don't understand this. I'm not changing something in a GameNode, I'm adding a GameNode to a collection, so the collection is changed and should send an event. You said the ItemsSource listens for CollectionChange, but the ItemsSource is the MultiBind which in turn is the collections.
Decept
Yes ItemsSource listens for event but your converter only gets called if Attributes or ChildNodes instances change, not when they fire an event of collection change.
Akash Kava
What do you mean with "if Attributes or ChildNodes instances change"? Doesn't adding an item change them or do you mean that I change them to an entirely different collection object?I tried making GameNode a INotifyPropertyChanged and manually fire OnPropertyChange("ChildNodes") after I added a new GameNode to ChildNodes. That did call my converter and updated the TreeView.
Decept
Attributes = something new (this changes attributes), but if attributes is a collection and if it fires CollectionChange event, binding will not detect it, binding will only detect Attributes= somethign new.
Akash Kava
I'm sorry to say that I never got it to work this way. Instead I changed my usage of the TreeView to proper MVVM style. Now each TreeView item is bound to a ItemViewModel which in turn is linked to the Model (GameNode). ItemViewModel combines Attributes and ChildNodes from the Model into 1 ObservableCollection that is bound to the TreeViewItem. Now everything is working correctly.
Decept