Note If you’re getting a sense of deja vu reading this, it’s because I posed this about 6 months ago, but it was lost when I was migrating it between blog back-ends. I’ve restored it now in it’s full…errr…..glory.
For large hierarchies the TreeView in the Silverlight tool-kit suffers some performance problems, because the elements (once loaded and expanded, such that they could be seen by the user if they scrolled the scroll-bar) aren’t virtualized as aggressively as they could be. The for example the hierarchy shown below: If Child Node 4 is expanded, pushing Child Node 5 outside the limits of the view port of the TreeView then Node 5, as well as any other children of Node 4 that aren’t directly visible, could be virtualized but currently aren’t by the Silverlight Treeview. When the number of invisible-but-loaded nodes is small the performance impact is negligible, but if Node 4 (in the example below) had 10,000 children, each with a complex data template applied to them the performance impact expanding the tree can be quite noticeable.
One approach to get around this is to recognize that a TreeView is just a List with indentation and expand/collapse semantics. By flattening your object hierarchy with an appropriate wrapper you can use the virtualization that is built into the ListBox, which happily does virtualize items that have disappeared off the top or the bottom of the list, by virtue of its use of the VirtualizingStackPanel that was added in Silverlight 3. http://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingstackpanel(VS.95).aspx
The sample provided does just that with two classes TreeViewModel<T> and NodeViewModel<T>. TreeViewModel<T> contains a Nodes collection, an ObservableCollection of NodeViewModel<T> which the list is bound to. The visibility of the expand/collapse arrow is bound to the presence of children. When a NodeViewModel<T> is expanded it calls back to the TreeViewModel<T> to tell the TreeViewModel to create a new set of NodeViewModels based on the children of the node being expanded, and to insert them directly after node being expanded in the current list of nodes. When the TreeViewModel makes new nodes it increments the indentation property, which is used to drive how far in the node is rendered when it is shown. When a NodeViewModel<T> is collapsed it again calls the TreeViewModel<T> and basically tells it to throw away all the nodes that come after me in the Nodes collection, until you reach one that has the same indentation as me (or you reach the end of the list), at which point the TreeViewModel will know that it has removed all the children of the item that was collapsed.
The sample provided is not a complete drop-in replacement for the TreeView control in the Silverlight toolkit. For one thing some of the expand/collapse semantics are a little different to a standard TreeView. For example if all the nodes in the tree are fully expanded, and the root node is collapsed and then re-expanded then the standard tree-view behaviour is for the tree to look the same as before the root node was collapsed, however this tree will only show the direct children of the root node after the root node is collapsed and then re-expanded, regardless of the state of the tree before. This is a side-effect of the way this implementation creates and destroys NodeViewModel<T> instances when a node is collapsed/expanded. When a NodeViewModel<T> is removed from the list of nodes the fact that it was previously expanded is lost. This could be changed by keeping instances of removed nodes around, but would complicate the expand/collapse methods in TreeViewModel<T>. Also some other TreeView standard keyboard short-cuts are missing (for example right arrow to expand a node, and left arrow to contract it) are not wired up. Also it is likely that this technique will become obsolete at some point when the TreeView in the Silverlight Toolkit catches up with the WPF one and implements this trick for itself.