Programmatically filtering the WPF TreeView
There will be filtering and multi selection support in the next iteration of my WPF TreeView, but based on a request on the Code Project forum, I decided to implement a simple filtering mechanism on the current version.
First of all, you can provide filtering without even touching the control base class by just applying the filter in your implementation of the abstract GetChildItems method. This method would effectively filter all items of the sample tree:
//returns subcategories that should be available through the tree public override ICollection<ShopCategory> GetChildItems(ShopCategory parent) { //create a filtered list List<ShopCategory> list = new List<ShopCategory>(); foreach(ShopCategory category in parent.SubCategories) { if ( ... ) list.Add(category); } return list; }
In order to have the tree react to changed filter conditions, calling the tree’s Refresh() method takes care of everything.
This approach is dead simple, and it has the advantage that only items that are supposed to be accessible on the tree are being processed by the control. On the other hand, it also means that you would have to recreate the tree every time the tree’s filter changes.
In order to provide an alternative, I also looked at filtering the tree on the UI level (filtering == just hide the filtered nodes). The following sample sample operates on the tree implementation of the sample application, and provides a property of type Predicate<ShopCategory>. In order to get it working, I needed to do 3 things:
- Apply the filter for new nodes that are being created
- Run the filter if a node is being expanded
- Refresh the tree if the filter is being set
I must say I’m quite satisfied – as the control provides me with virtual methods to intercept everything, the whole thing took about 2 minutes to set up 🙂
private Predicate<ShopCategory> filter = null; /// <summary> /// Defines a filter for items that are bound to the tree. Set to /// null in order to disable filtering. /// </summary> public Predicate<ShopCategory> Filter { get { return filter; } set { filter = value; //recreate the tree in order to apply the filter on //all currently visible nodes //-> of course, this could be optimized, but it does the job Refresh(GetTreeLayout()); } } /// <summary> /// Applies the filter on all child nodes. /// </summary> /// <param name="treeNode"></param> protected override void OnNodeExpanded(TreeViewItem treeNode) { //make sure child nodes are being created base.OnNodeExpanded(treeNode); //apply filter foreach (TreeViewItem childNode in treeNode.Items) { ApplyFilter(childNode, (ShopCategory)childNode.Header); } } /// <summary> /// Immediately applies the filter on newly created items. This /// is somewhat redundant (as we're also handling <see cref="OnNodeExpanded"/>), /// but ensures we also consider root nodes. /// </summary> /// <param name="item"></param> /// <returns></returns> protected override TreeViewItem CreateTreeViewItem(ShopCategory item) { //delegate node creation to base class TreeViewItem node = base.CreateTreeViewItem(item); //apply the filter and return the node ApplyFilter(node, item); return node; } /// <summary> /// Filters categories if the <see cref="Filter"/> property /// is set by simply setting the <see cref="TreeViewItem.Visibility"/> /// property to <see cref="Visibility.Collapsed"/> if the item does /// not match the filter. /// </summary> private void ApplyFilter(TreeViewItem node, ShopCategory item) { bool visible = filter == null || filter(item); node.Visibility = visible ? Visibility.Visible : Visibility.Collapsed; }
You can try it out by adding the above code to the sample tree (CategoryTree.cs), and setting the Filter property in an event handler of the sample app. Note that OnNodeExpanded is already overridden, so you’ll end up with two duplicate methods if you paste in the snippet.
Have you implemented the next iteration of your WPF TreeView that supports filtering and multi selection? If so, can you post it on your blog. If not, are you planning to implement it soon?
Thanks
No, the filtering as provided works fine for me and I currently have no need of further improvements (or a tree control at all). Sorry :/