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.