Home > Open Source, WPF TreeView > A versatile WPF TreeView control

A versatile WPF TreeView control

January 24th, 2008

A tutorial is now available on Code Project, so check the article for a detailed overview. And please leave your rating if you like the control 🙂
http://www.codeproject.com/KB/WPF/versatile_treeview.aspx

Update: The latest version is currently only available through the download link below. I’ll update the CodeProject article once the current filtering mechanism has been rewritten:

Download: wpf-treeview.zip (Current version: 1.0.7, updated 2008.04.06)

TreeView example This is a little something I’ve been working on for a while: A replacement (or better: enhancement) of WPF’s built-in TreeView control.

I became aware of the default control’s limitations during my last project – I naturally started with hierarchical data templates, but was soon confronted with quite a few issues: I missed a simple API to control the tree, and styling of the tree’s nodes proved hard as well. Furthermore, WPF’s TreeView tends to fire all sorts of SelectedItemChanged events if it’s being refreshed or rebound, which caused side-effects with TwoWay data binding.

However, instead of posting a rant that probably nobody would ever read (let alone care about), I worked on an alternative. Here’s the tree’s main features at a glance:

  • Simple declaration:
    <local:ProductTree x:Name="MyTree"
                       Items="{Binding Source={StaticResource Shop},
                              Path=Products}"
                       SelectedItem="{Binding ElementName=MyProductList,
                              Path=ActiveItem, Mode=TwoWay}"
                       NodeContextMenu="{StaticResource CategoryMenu}"
                       TreeNodeStyle="{StaticResource SimpleFolders}"
                       TreeStyle="{StaticResource SimpleTreeStyle}"
                       SelectedItemChanged="OnSelectedItemChanged"
    />
  • Simple and type safe API:
    //bind flat list of business objects to tree
    List<Product> products = GetProducts();
    myTree.Items = products;
    
    //select a given item
    Product foo = GetBestSellingProduct();
    myTree.SelectedItem = foo; 
    
    //SelectedItem is of type Product - no casts required
    Product bar = myTree.SelectedItem;
  • Lazy loading support – does not create tree nodes until the parent node is expanded. Also provides the option to automatically clear invisible tree nodes. This allows either virtualized trees in case getting data is expensive, or low memory trees that keep the number of tree nodes at a minimum.
  • Simple sorting.
  • Convenient context menu handling for tree nodes
  • Optional root node which is not dependent on the tree’s bound items
  • Simple styling on every level: Tree, TreeViewItem, or bound items (via DataTemplates).
  • Tree layout can be cached, saved and reapplied.
  • Access to tree nodes (TreeViewItem) through bound items.
  • AutoCollapse feature / ExpandAll / CollapseAll methods

All this goodness comes at a price: The TreeViewBase class that provides this functionality, is abstract. This means you have to write a little code yourself. However, you’ll probably manage with 3 lines of code, as the base control just needs to know 3 things:

  • How to generate an identifier for a given tree node
  • How to access a bound item’s childs, if there are any
  • How to access a bound item’s parent, if there is one

Here’s the complete implementation of the sample application’s tree control:

//a tree control that handles only ShopCategory objects
public class CategoryTree : TreeViewBase<ShopCategory>
{

  //the sample uses the category's name as the identifier
  public override string GetItemKey(ShopCategory item)
  {
    return item.CategoryName;
  }

  //returns subcategories that should be available through the tree
  public override ICollection<ShopCategory>
                             GetChildItems(ShopCategory parent)
  {
    return parent.SubCategories;
  }

  //get the parent category, or null if it's a root category
  public override ShopCategory GetParentItem(ShopCategory item)
  {
    return item.ParentCategory;
  }

I’m planning to write a CodeProject article for this one, but for now, it’s only available through my place without a tutorial. However: The library comes with a sample project that shows pretty much all features of the control. Project format is currently VS2008 only, but binaries which target .NET 3.0 are included. Enjoy!

Sample Application


  1. Horst
    January 27th, 2008 at 18:45 | #1

    Wow – and thank you very much! I’m really happy to have found your blog.

  2. Craig
    February 18th, 2008 at 04:57 | #2

    Fantastic, thanks for sharing this!

  3. Michael
    February 20th, 2008 at 14:17 | #3

    Amazing stuff! Kudos to you…
    I do have a treeviewbase question for you:
    To attempt showing a hierarchy of parent-child-grandchild in a treeview control, would you use 3 treeviewbase classes? E.g. If you had a collection of Client objects, each containing a property for a collection of Product objects, each Product in turn having a collection of Parts objects…
    So something like Clients[0].Products[0].Parts[0].Name will give you the name of the first part of the first product of the first client…
    How would you represent this in a treeview?

  4. February 20th, 2008 at 15:08 | #4

    Michael: You just need one TreeViewBase – basically, you can bind arbitrary objects to it. However, in order to make it easier to handle the different items, I’d recommend you have them implement a common interface or derive from a common base class.

    Beyond that, nothing does change: Just bind your top-level collection (in your case an IEnumerable) and the tree takes care of the rest 🙂

    In case you need further assistance, you can contact me directly via eMail (see under “Business”). Cheers!

  5. Walt
    February 29th, 2008 at 21:34 | #5

    Great stuff here. I do have one question. I’m getting an error in the designer on the TreeResources.xaml. The error: “Type reference cannot find public type named ‘ShopCategory'”.
    The sample runs without a problem. This is only an issue in the VS 2008 designer.
    Any Ideas?
    Thanks for the great sample.

  6. February 29th, 2008 at 22:23 | #6

    Hi Walt
    It’s ridiculously easy to break the WPF designer in VS2008 – it might have to do with a style I assigned or a resource declaration. As a matter of fact, I have no idea – I don’t use the designer anymore (XAML view only) as it annoys the hell out of me – sorry 😉

  7. March 5th, 2008 at 13:35 | #7

    Dear Philip, first of all, I really love this control. I read your post regarding this control both in here and in CodeProject. Kudo for you.

    However, I love to know if there is a simpler tutorial, especially on the part that we need to create the XAML resource file in order to customize the tree appearance. It is quite difficult for me to write the XAML resource (that has DataTemplate tag), because the IDE always complained that the tag is not being defined… I’m not sure.

    Secondly, will there be any enhancement to this nice control. Will it be placed in CodePlex, for example?

    Thanks.

  8. March 5th, 2008 at 18:43 | #8

    Nordin: Thanks for your feedback – I’m glad you like the control!

    Unfortunately, the VS designer breaks very easily and the sample uses various external resources. However, data templates are written in XAML anyway and creating them works the same as for the standard WPF TreeView control. For a minimal style, check the tutorial at CodeProject, it includes a *very* simple data template (chapter “Data Templates”). The whole data template is only 6 lines of XAML so you should easily understand what it’s doing 🙂

    And of course, there will be enhancements – I’ve planned built-in filtering and multi-selection support for the next iteration. I’ll update the CodeProject article (and post here, of course) as soon as it’s done. No CodePlex, however 😉

    Cheers,
    Philipp

  9. Brian
    April 2nd, 2008 at 15:28 | #9

    Philipp,

    Awesome control. Only problem is that I’m binding to an ObservableCollection and your treeview isn’t picking up on new items added there. So, for your example, adding items to the root node. It works great in the rest of the hierarchy – do you know off hand a fix for that? I see that your “Items” is IEnumerable – does that affect the binding?

    Thanks.

  10. April 3rd, 2008 at 12:30 | #10

    Hi Brian

    This feature is already implemented in my current release (there’s a new ObserveRootItems dependency property which defaults to true). I’m pretty sure I’ll release this weekend, but if you want to get your hands on the current build, just drop me a mail 🙂

    Cheers,
    Philipp

  11. April 14th, 2008 at 18:21 | #11

    Hi Philipp
    im very impressed about your work. It is a well designed and a nearly perfect implementation. Thats why i have the control implemented in my current application. The costumization to my needs was really easy.
    Now i have a problem with the implementation of context menus. Is there a simple way to add different contect menus to the nodes?
    It would be very nice if you find the time to give me a answer.
    Thanks in advance — Toni

  12. April 14th, 2008 at 18:36 | #12

    Hi Toni

    Thanks for your kind words! Depending on the amount of nodes/menues, there are different strategies:

    • You could set the context menu of a node using a style or trigger. This would be a clean and declarative solution that even cooperates with the NodeContextMenu property: If the context menu of a node is set, NodeContextMenu is ignored, otherwise applied.
    • You can assign a context menu to specific or all nodes during creation by overriding the CreateTreeViewItem method. Again, you can still use NodeContextMenu for a default context menu.
    • Intercept the right-clicks on tree nodes and set context menus on demand. Have a look at the OnRightMouseButtonUp method of the base class – you should be able to use that code in order to define your own event listener that assigns your custom context menus.

    Hope that helps,
    Philipp

  13. April 15th, 2008 at 13:36 | #13

    Hi Philipp
    thanks for your quick reply.
    Overriding the ‘CreateTreeViewItem’ method was the perfect solution for me.

    have a nice day
    Toni

  14. Justin
    July 22nd, 2008 at 18:10 | #14

    Great control. Good work on implementing a very usable set of features. To answer Walt’s question about TreeResources.xaml breaking: I changed the Assembly Name (in project properties) to remove the space, assuming XAML doesn’t like spaces. I then rebuilt the project and re-created the “shop” namespace reference in TreeResources.xaml. You may also be able to just remove the “;assembly=” from the end of the shop namespace declaration.

    Justin

  15. Matthias Sandmann
    November 16th, 2008 at 11:27 | #15

    Great Job,
    good work and a very usefull one.
    Have made some tests and will use this tree in my upcoming projects.
    Big thanks to the author!

    Matthias

  1. No trackbacks yet.
Comments are closed.