in

ASP.NET Weblogs

-[Danny Chen]- Blog of an ASP.NET QA tester

Tips and info about Site Navigation, ImageMap, Menu and other cool ASP.NET v2.0 features.

Displaying Sibling Nodes in a Tree or Menu

I got an email with this question and a request that I post the solution on my blog so here it is:

Consider a sitemap with the following structure: 

The nodes "c", "e", and "h" (highlighted in yellow) are considered sibling nodes because they have the same parent.

Q: Can I display all the sibling nodes of the current node in a TreeView or Menu?

The answer is yes, and it's easy: 

<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" ShowStartingNode="False" StartFromCurrentNode="True" StartingNodeOffset="-1" />

However, a more challenging question is:  Can I display all the nodes at the same depth as the current node (lets assume this is node "c")?  This would include the nodes "c", "e", "h" but also "r", and "u" (all the ones highlighted in green).

The answer is yes (of course) but it has to be done after-the-fact in the Tree or Menu.  There are a couple "tricks", however.  Here's the code for a TreeView:

    ' Databound event handler for a TreeView   
    Protected Sub TreeView_DataBound(ByVal sender As Object, ByVal e As System.EventArgs)
        Dim Tree As TreeView = sender
       
        ' Only prune the tree if a node is selected
        If Tree.SelectedNode IsNot Nothing Then
           
            Dim childNodes As New Generic.List(Of TreeNode)()
            Dim childNodeCollection As New TreeNodeCollection()
           
            FindNodesAtDepth(Tree.Nodes, Tree.SelectedNode.Depth, childNodes)
                       
            ' Copy from List(of TreeNode) to TreeNodeCollection
            For Each node As TreeNode In childNodes
                childNodeCollection.Add(node)
            Next
           
            ' Clear the TreeView nodes and add the nodes we found
            Tree.Nodes.Clear()
            CloneTreeNodeHierarchyRecursive(childNodeCollection, Tree.Nodes)
           
        End If
       
    End Sub

  This function, conceptually, is pretty straight forward.  Collect a list of the Nodes at the depth of the selected node, clear the nodes in the tree and add them back into the Tree.  However, in practice, the helpers needed make it a bit more complicated. 

 

    ' Recursively searches the tree for all nodes at a given depth
    ' and adds them to a list of TreeNodes
    Private Sub FindNodesAtDepth(ByVal collection As TreeNodeCollection, _
                                 ByVal targetDepth As Integer, _
                                 ByVal targetNodes As Generic.List(Of TreeNode))
       
        For Each node As TreeNode In collection
            If node.Depth = targetDepth Then
                targetNodes.Add(node)
            ElseIf node.Depth < targetDepth Then
                FindNodesAtDepth(node.ChildNodes, targetDepth, targetNodes)
            End If
        Next
    End Sub

There's not much to say about this function, it's pretty simple.  It's a depth-first recursive function that creates a list of all the nodes with Depth=targetDepth.

 

    ' Recursively creates a clone of a given TreeNode Hierarchy   
    Private Sub CloneTreeNodeHierarchyRecursive(ByVal inCollection As TreeNodeCollection, _
                                                ByVal outCollection As TreeNodeCollection)
       
        For Each node As TreeNode In inCollection
            Dim clonedNode As TreeNode = CType(node, ICloneable).Clone()           
            outCollection.Add(clonedNode)
           
            If node.ChildNodes.Count > 0 Then
                CloneTreeNodeHierarchyRecursive(node.ChildNodes, clonedNode.ChildNodes)
            End If                       
        Next
    End Sub

This function is interesting and I had some fun writing it.  It's also a depth first recursive function.  It's job is to create a copy of the hierarchy. 

Some quick Q&A about this solution:

Q: Why did I copy all the nodes into a list and then to a collection, why not just pass the collection into FindNodesAtDepth()?

A: TreeNodes (and MenuItems) can only live in one collection at a time.  When they are added to a collection, they are removed from any collection they previously were in.  This causes the initial collection the Node was in to change and .NET does not allow a collection to change while it's being enumerated.

Q: Why did I go through all the business of recursively cloning all the nodes instead of adding them directly to the Tree?

A: This is because, currently, TreeNodes (and MenuItems) cache the Depth property once it is calculated.  At this time, there is no straight forward way to tell an entire hierarchy to recalculate their depth except to create a new node which hasn't calculated it's depth.  If I hadn't done this, the TreeNodes would actually render themselves deeper than expected. 

Link to VB Code Listing
Link to C# Code Listing

Comments

 

KSpriggs said:

Very good.  I wish many of the answers on the web were so well expressed.

October 26, 2007 7:58 PM

Leave a Comment

(required)  
(optional)
(required)  
Add