Tuesday, June 23, 2009

How to fake a TreeNodeCollection subclass in .NET

If you've ever had reason to try to extend the standard Microsoft web TreeView control, you will have no doubt noticed that MS was quite unkind to you and sealed (or declared NotInheritable for you VB.NET types) the System.Web.UI.WebControls.TreeNodeCollection class.

The problem arises when you want to overload the default behavior that is implemented by the TreeNodeCollection class. For example, when a node was added to my TreeView class (via the TreeView.Nodes.add method), I needed to be able to analyze it for the ultimate purpose of my subclass.

However, this was not possible because the TreeNodeCollection class is sealed, so I wasn't able to inherit from it and overload the add method behavior as I should have been able to do.

There are always possibilities.



Anyone who knows me knows I don't give up easily (if ever), and I eventually plowed through many false starts but hit upon a solution.

I decided to wrap the underlying TreeNodeCollection class with my own, and overload the Nodes property on the TreeView class, and the ChildNodes property on the TreeNode class.

The Wrapper.



Here's what the wrapper looks like:
Public Class MyTreeNodeCollection

  Private mtvwChildNodes As System.Web.UI.WebControls.TreeNodeCollection
  Private mMyTreeViewOwner As IMyNodeContainer

  Public Sub New(ByVal Owner As IMyNodeContainer, _
       ByVal TreeViewChildren As System.Web.UI.WebControls.TreeNodeCollection)
     mtvwChildNodes = TreeViewChildren
     mMyTreeViewOwner = Owner
  End Sub

  Public Sub Add(ByVal child As MyTreeNode)
     mtvwChildNodes.Add(CType(child, System.Web.UI.WebControls.TreeNode))
     mMyTreeViewOwner.RegisterNodeForLookup(child)
  End Sub
End Class

As you can see, the constructor takes the real, underlying TreeNodeCollection to pass all node to prior to being "registered" (analyzed). It also takes something that implements the IMyNodeContainer interface.

The IMyNodeContainer interface.



I had to implement this because I wanted to be able to use this wrapper class for both TreeView and TreeNode objects, but the signature of their properties is different - TreeView.Nodes and TreeNode.ChildNodes, respectively. So I opted for the interface to keep things clean.

Here's what the interface looks like:

Public Interface IMyNodeContainer

    Sub RegisterNodeForLookup(ByVal node As MyTreeNode)
    ReadOnly Property Nodes() As MyTreeNodeCollection

End Interface

Wrapping things up.



This is where the magic happens.

To put the wrapper (MyTreeNodeCollection) in place, I overload the collection getting properties in the base class - TreeView.Nodes and TreeNode.ChildNodes, respectively, like so:

Public Overloads ReadOnly Property Nodes() As MyTreeNodeCollection Implements IMyNodeContainer.Nodes
  Get
    Return mNodeCollection
  End Get
End Property

So, for those of you playing along at home, when the developer calls .Nodes on an instance of MyTreeView class, an instance of MyTreeNodeCollection is returned and the resulting add method in performed on the underlying TreeNodeCollection, thusly:

Public Sub Add(ByVal child As MyTreeNode)
    mtvwChildNodes.Add(CType(child, System.Web.UI.WebControls.TreeNode))
    mMyTreeViewOwner.RegisterNodeForLookup(child)
End Sub

You can see why I had to use the IMyNodeContainer interface here. The RegisterNodeForLookup performs the functionality I was originally trying to subclass the TreeNodeCollection for.

No comments:

Post a Comment