TreeView Part 2 – Traversing the TreeView

In Justin Cook’s last article, he explained some uses of the new TreeView control available with ASP.Net 2.0 and how to populate it with data from a hierarchical database table. He now discusses a very important piece of functionality, the ability to traverse the tree to find and select a pre-specified node.

So by now you should be somewhat comfortable working with the TreeView control. You’ve dropped it into your web page, and you’ve used the recursive function provided in my last article to populate it. You’ve also tweaked the visual aspects of the object. So now you have a fully functional, hierarchical tree structure in your web application, which can be used to hold such things as an employee organizational structure, a product list, a site structure, or whatever else your creative mind can conjure up.

I did not go into any detail about retrieving data about a specific node. I basically left it that once you highlight a specific item/node, there is an empty method responsible for getting the data for that node. This is basic data retrieval, and you should have no trouble crafting the method to get exactly the data you need.

The shortfall of last article was that you still needed to click on an employee to get their info. It’s very common to have other ways to access that data, such as when you receive an email with a direct link to that employee’s info. You shouldn’t have to navigate through the tree in such a case. You should be taken directly to the information, and the tree should indicate this by showing the employee as selected.

Now if it was a simple selection list, or any other collection based control we were dealing with, we could simply set the ‘SelectedValue’ property, and the object would find the item with that value and select it. But due to the hierarchical nature of the TreeView, we can’t simply find a node by it’s value. This is because each node, including the root node, has a set of child nodes that would need to specified and searched. For example, if you have a TreeView called EmployeeList, and you wanted to find a single employee, you would have to place a try & catch block to try and select at each node level. Needless to say, very inefficient, as well as virtually impossible to maintain programmatically if you have a dynamic tree being populated from a database each time.

So there must be a better way to accomplish this. And there is! Just as we used a recursive function to load each node and search and list each child node, we’re going to do the same thing to search and select a node from the tree. This way we can find the node no matter how large or deep the tree grows. So let’s get into it, starting with the email (or otherwise) direct link to the specific data. As in the last article’s example, we’ll use an employee list as the example.

{mospagebreak title=Step 1 – Parse the Link}

This is a fairly common task you’ll need to accomplish, but I’ll include it to provide a complete solution. You can obviously modify it to meet the needs of your application.

So to begin, a user has received an email with a link to their employee data, which could look like the following:
http://127.0.0.1/DirectoryApp/EmployeeInfo.aspx?EmployeeID=3376

Once they click on the link, they’ll be taken to EmployeeInfo.aspx. This is the file you’ve plugged the TreeView control into.

So fire up you IDE or text editor and open up that file.

At this point the only object we’ve declared is the DataSet that holds the employee data retrieved from the database. We’ll need to add the following:

protected Int32 EmployeeID;
protected Boolean NodeFound = false;

The EmployeeID integer will obviously hold the unique identifier, which we’ll pull from the query string. The NodeFound is the Boolean flag we’ll use to determine whether or not we’ve located the node of that value in the tree, and when to exit the recursive function. This will prevent infinite recursion, and will also enable us to cut down the method signature, as well won’t have to pass the flag in the parameter list.

Now we need to modify the page_load method to parse the query string. At this point it’s doing only two things on first load: pulling the data, and loading the TreeView. Let’s now modify to look like the following:

void page_load(object sender, EventArgs e)
{
  if (!Page.IsPostBack)
  {
      // first load DataSet
      LoadDataSet();

      // now build list
      BuildEmployeeList(EmployeeList.Nodes, 0);

      try
      {
        EmployeeID =
        Convert.ToInt32(Request.QueryString[“EmployeeID”]);
      }
      catch (FormatException fe)
      {
        Response.Write(“Invalid employee ID,
           please select one from the list”);
      }
      catch (ArgumentNullException)
      
{ }

      if (EmployeeID > 0)
      {
          ViewState[“EmployeeID”] = EmployeeID.ToString();
          SelectNodeByVal(EmployeeList.Nodes,
            EmployeeID.ToString());
            SelectEmployee(EmployeeList, e);
       }
  }
}

So let me explain what we just implemented. First of all, we pull the EmployeeID value out of the selection string, and convert it to an Int32. We put this in a try/catch block to catch invalid or null values, in which case we could just print out a quick error and still allow the user to select an employee from the list.

Next, we store the EmployeeID in the viewstate. This is a very important step if the page will be doing any postbacks. That’s because the global variable EmployeeID only has the same life cycle as the page itself, and therefore the value will disappear once it is sent to the client browser. We can store this variable (and others) in the viewstate, and retrieve them each time we perform another action or do some sort of action. This way we no longer have to employ the use of hidden form fields, which can easily be compromised by hackers.

Now we hand off the processing to the meat of this article, the method that traverses the tree and find the node we’re interested in. Similar to the node loading method, we first specify the root’s set of child nodes to search, but we also send through the string to search for, this time the EmployeeID integer converted to a string.

Last but not least, we call the SelectEmployee method to pull the data, the same as we did before. You shouldn’t need to make any modifications on that method so long as you’re basing the data pull on the selected node value of the tree, so let’s now move into the tree searching function.

{mospagebreak title=Step 2 – Find the Node}

Here’s the structure of the method, in full. I’ll place step identifiers in the code and explain afterwards, to make it easier for you to copy and paste:

void FindNodeByVal(TreeNodeCollection nodes, string SearchValue)
{
  // step 1
  for (int i = 0; i < nodes.Count; i++)
  {
    // step 2
    if (nodes[i].Value == SearchValue)
    {
      // step 3
      nodes[i].Select();

      // step 4
      NodeFound = true;
      return;
    }
    else
    {
      // step 5
      NodeFound = false;
    }

    // step 6
    nodes[i].Expand();

    // step 7
    FindNodeByVal(nodes[i].ChildNodes, SearchValue);

    // step 8
    if (NodeFound)
    {
     
return;
    }

    // step 9
    nodes[i].Collapse();

    return;
  }
}

Ok, here’s the explanation:

  1. Here we simply start a loop through all of the current collection of nodes. The first time through this will be the root node.
  2. This is where the comparison takes place between the current node’s value and that of the search string. If it’s a match, steps 3 & 4 take place, and the method exits. Otherwise, steps 5 through 9 occur.
  3. The node is set as selected. This modifies the SelectedNode property of the tree, and the visual formatting is also invoked. The SelectedNodeChanged event node also fires.
  4. The Boolean flag is tripped, indicating that the node has been located.
  5. This is just to ensure that the flag remains as false.
  6. So at this point the node hasn’t been found. So we want to search through all the child nodes. But it’s also very important to show this visually, to ‘open’ the path to the node.
  7. Once we’ve opened (displayed) the child nodes, we call the same method recursively, this time specifying a new node set to search.
  8. At this point, the method has executed on all child nodes, and one of them is the right node. So we leave the parent node expanded, all the way back to the root, and exit the function.
  9. Here the child nodes have been searched, but the node was not located. Since there is no need to keep this branch open and clutter the screen, we can collapse it. Since this is the end of the method, it will simply return, and carry on searching the sibling nodes.

Conclusion

I am somewhat surprised that this control didn’t ship with any inbuilt mechanism for performing this extremely vital search function. But that’s OK, as this method is extremely efficient. You could also modify it if need be to search by the text of the node, not the value. If you wanted, you could also conditionally leave the full tree open upon searching, though I don’t recommend that! As a side benefit of the last two articles, you should also now be pretty comfortable with recursion and hierarchy, two somewhat frustrating concepts to first wrap your mind around.

No doubt you’ll find the TreeView a very useful and much-needed addition to the ASP.Net class library. Hopefully these two tutorials will enable you to use the control easily and effectively in your web application!

One thought on “TreeView Part 2 – Traversing the TreeView

  1. This function was exactly what I was looking for
    After half an hour of debugging I think I found an error, the for loop was not looping after returning from a child node.

    The section below should be changed.
    // step 9
    nodes[i].Collapse();

    return;
    }
    }

    It should read:-
    // step 9
    nodes[i].Collapse();
    }
    return;

    }

    It seems to work for me!:P

[gp-comments width="770" linklove="off" ]