Steve Steve - 2 months ago 25
C# Question

TreeView Items changed event

To add multi select support to the native WPF tree view I had to add a custom dependency property which stores the multi selected items. This works great until the tree's Items started to change.

For example in the initial tree there is one item A. I selected it, it gets stored in

MultiSelectedItems
list. Then I removed item A and added item B. (through ViewModel
ObsevableCollection
binding)

I need to find a way to remove item A from
MultiSelectedItems
list when this happens.

I am unable to find an event for this. The closest I get is
ItemContainerGenerator.ItemsChanged
event. But this event only fires for root level nodes (does not fire for its hierarchy children)

Answer

The key idea to solve this problem is to detect items changed event within each node instead of at the tree level.

I inherited the TreeViewItem class

public class MultiSelectTreeViewItem : TreeViewItem
{
    object _originalHeader;
    protected override void OnHeaderChanged(object oldHeader, object newHeader)
    {
        base.OnHeaderChanged(oldHeader, newHeader);
        //.NET 4.5 use BindingOperations.DisconnectedSource
        if (newHeader.ToString() != "{DisconnectedItem}")
            _originalHeader = newHeader;
    }

    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
    {
        base.OnItemsChanged(e);

        if (Header.ToString() == "{DisconnectedItem}" && _originalHeader != null && e.Action == NotifyCollectionChangedAction.Reset)
        {
            //Find the parent Tree View and remove this from MultiSelectedList
        }
    }


    protected override DependencyObject GetContainerForItemOverride()
    {
        return new MultiSelectTreeViewItem();
    }

    protected override bool IsItemItsOwnContainerOverride(object item)
    {
        return item is MultiSelectTreeViewItem;
    }
}

In the inherited TreeView

    protected override DependencyObject GetContainerForItemOverride()
    {
        return new MultiSelectTreeViewItem();
    }

    protected override bool IsItemItsOwnContainerOverride(object item)
    {
        return item is MultiSelectTreeViewItem;
    }

When the node got removed, its header will be set to a sentinel object called DisconnectedItem, and the Items Changed event will be NotifyCollectionChangedAction.Reset.

Note that if you did a List.Clear() the NotifyCollectionChangedAction.Remove event will not be fire, only NotifyCollectionChangedAction.Reset will. So I find it the most reliable way to detect node removal.

One catch is that if the node has not been rendered (parent has never been expanded) then this event will not fire.