Search code examples
c#wpftreeview

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 ObservableCollection 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).


Solution

  • 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.