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