Introduction
Have you ever wanted to sort a TreeView
control by trying to inherit from TreeViewCollection
(the data type of the Nodes
property)? After writing an entire subclass doing just this, I tried to compile my work only to find out that there is a problem extending collections, see this MSDN post for more information.
Background
So what now? The forum post offered little in the way of ideas. Without a way to extend the TreeViewCollection
class, the next most logical choice is to put methods in either the TreeView
subclass or in the callee class. Either way, you end up with a method signature that looks like this...
Add(TreeNodeCollection nodes, TreeNode node)
The first thing I didn't like was the two parameter method signature. If I had been able to extend the TreeViewCollection
class (or was not sorting), I would only be passing the new TreeNode
to the Add
method (of the Nodes
property).
The choice of putting a method like this in the callee class was the first option to go. I have multiple classes (forms classes in my) writing to the TreeView
control. I also didn't like the choice of placing it in TreeView
control, because essentially it's a static
method (doesn't use any instance variables).
If there was only a way to add methods to the TreeViewCollection
class... After thinking a while, I came up with an idea that is complete syntax candy to accessing the Nodes
property directly. I was inspired by indexers to accomplish what I was looking for. For an introduction to Indexers, check out this tutorial.
Indexers are wonderfully flexible in C-Sharp. Typically, indexers are used to access a particular element of a collection or data set. My idea was to use an index to pass in an existing collection and place it in a wrapper class (the indexer's return type).
Using the Code
First I started by adding a property to my subclass of the TreeView
control. The property was called SortedNodes
and was of type InsertionSortWrapper
. InsertionSortWrapper
is basically the method I was planning to have in the subclass of TreeViewCollection
(with override and virtual modifiers removed). Adding an indexer allowed convenient wrapping of any Nodes
collection using the following syntax...
private void buttonPopulate_Click(object sender, EventArgs e)
{
for (int i = 0; i < 100; i++)
{
TreeNode topLevelNode = new TreeNode(RandomString());
sortedTreeView.SortedNodes.Add(topLevelNode);
for (int j = 0; j < 20; j++)
{
TreeNode childNode = new TreeNode(RandomString());
sortedTreeView.SortedNodes[topLevelNode.Nodes].Add(childNode);
}
}
}
This code is made possible by using an indexer, passing the top level Nodes
property to the constructor of InsertionSortWrapper
, and making that your SortedNodes
property in the TreeView
. The code looks like this...
public sealed class InsertionSortWrapper
{
public InsertionSortWrapper this[TreeNodeCollection collection]
{
get
{
return new InsertionSortWrapper(collection);
}
}
private TreeNodeCollection Nodes
{
get;
set;
}
public InsertionSortWrapper(TreeNodeCollection actualNodes)
{
Nodes = actualNodes;
}
}
In the demo, I provided an insertion sort implementation. The wrapper provides Add()
methods that have the same signature as in TreeViewCollection
. The public
methods in the wrapper use the private Nodes
property to do all the real work on the actual nodes; finding the proper place to insert, and inserting the node there. You could implement additional methods or any sorting you choose. While you wouldn't have to use the same signatures as the methods in TreeViewCollection
, it is the primary reason I had for implementing this. Here is the core of the insertion sort...
public int Add(TreeNode node, bool flagOnTop)
{
if (flagOnTop)
node.Tag = FLAG_ONTOP;
else
node.Tag = FLAG_NONE;
int index = GetInsertionIndex(node);
Nodes.Insert(index, node);
return index;
}
private int GetInsertionIndex(TreeNode node)
{
bool newNodeIsOnTop = ((((Int32)node.Tag) & FLAG_ONTOP) > 0);
for (int i = 0; i < Nodes.Count; i++)
{
TreeNodeCompareResult result =
TreeNodeCompare(Nodes[i].Text, node.Text);
bool testNodeIsOnTop = ((((Int32)Nodes[i].Tag) & FLAG_ONTOP) > 0);
if (!newNodeIsOnTop && testNodeIsOnTop)
continue;
if (newNodeIsOnTop && !testNodeIsOnTop)
return i;
if (result == TreeNodeCompareResult.LessThan)
continue;
if (result != TreeNodeCompareResult.LessThan)
return i;
}
return Nodes.Count;
}
History
- 17 November 2009 - Original post