Display Hierarchical Data with TreeView in ASP.NET
Now that ASP.NET 2.0 is about to be released to manufacturing, we ASP.NET developers have very interesting times ahead of us. Along with the new Framework, we have a major set of new controls to use and to ease our development work. One of the new controls, also my favorite, is the TreeView control. Microsoft has developed it based on feedback which arose from the release of the TreeView control in the open-source IE Web Controls package, targeted at the previous version of ASP.NET. TreeView has not just been rewritten; it now also includes a major set of new features such as support for client-side populating, on-demand populating, postback events, hyper link navigation, and a lot more.
Sample
I’m going to provide a simple example how to display hierarchical data from SQL Server database in the TreeView. A requirement is that the implementation should not be dependant on the hierarchy level in the database. It means that the TreeView implementation should be capable of displaying data from any level, no matter how deep.
Let’s assume we have TreeViewSampleDB database with the SampleCategories table as follows.
Figure #1
Note: It doesn’t matter if you use SQL Server 2005 or a previous version to try this sample.
Listing #1
<asp:TreeView
ID="TreeView1"
ExpandDepth="0"
PopulateNodesFromClient="true"
ShowLines="true"
ShowExpandCollapse="true"
runat="server" />
This means that we are setting the initial expand depth of the TreeView to the first level of nodes. We want to use the client-side node populating feature so that we can provide our users with a better usage experience. Note that this way, no postbacks are involved. We also want to show lines between the nodes and the expand/collapse icon if our tree nodes have child nodes.
Next, we start looking at the code in the TreeViewVB.aspx.vb file. We want to populate the root level. Here’s the code.
Listing #2
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not Page.IsPostBack Then
PopulateRootLevel()
End If
End Sub
Private Sub PopulateRootLevel()
Dim objConn As New SqlConnection(_
"server=JOTEKE\SQLExpress;Trusted_Connection=true;DATABASE=TreeViewSampleDB")
Dim objCommand As New SqlCommand("select id,title,(select count(*) FROM SampleCategories " _
& "WHERE parentid=sc.id) childnodecount FROM SampleCategories sc where parentID IS NULL", _
objConn)
Dim da As New SqlDataAdapter(objCommand)
Dim dt As New DataTable()
da.Fill(dt)
PopulateNodes(dt, TreeView1.Nodes)
End Sub
This happens by connecting to the database, querying the first set of nodes (having null as the parent id), and creating TreeNode objects with the PopulateNodes routine, which follows next.
Listing #3
Private Sub PopulateNodes(ByVal dt As DataTable, _
ByVal nodes As TreeNodeCollection)
For Each dr As DataRow In dt.Rows
Dim tn As New TreeNode()
tn.Text = dr("title").ToString()
tn.Value = dr("id").ToString()
nodes.Add(tn)
'If node has child nodes, then enable on-demand populating
tn.PopulateOnDemand = (CInt(dr("childnodecount")) > 0)
Next
End Sub
Here’s an important piece of logic. The PopulateNodes method iterates through all rows in the DataTable, and if the given node has child nodes, it sets the PopulateOnDemand property of the TreeNode object according to that. This has the effect that the expand/collapse icon is shown if the node has child nodes.
Next, we want to create the routine to populate the child nodes of a given node. This happens with the PopulateSubLevel method.
Listing #4
Private Sub PopulateSubLevel(ByVal parentid As Integer, _
ByVal parentNode As TreeNode)
Dim objConn As New SqlConnection(_
"server=JOTEKE\SQLExpress;Trusted_Connection=true;DATABASE=TreeViewSampleDB")
Dim objCommand As New SqlCommand("select id,title,(select count(*) FROM SampleCategories " _
& "WHERE parentid=sc.id) childnodecount FROM SampleCategories sc where parentID=@parentID", _
objConn)
objCommand.Parameters.Add("@parentID", SqlDbType.Int).Value = parentid
Dim da As New SqlDataAdapter(objCommand)
Dim dt As New DataTable()
da.Fill(dt)
PopulateNodes(dt, parentNode.ChildNodes)
End Sub
Here, the idea is the same as with the root level, but with the distinction that only child nodes of the given node are queried and populated with the PopulateNodes method (described earlier). The trick to triggering the populating of the child nodes is as follows.
Listing #5
Protected Sub TreeView1_TreeNodePopulate(ByVal sender As Object, _
ByVal e As System.Web.UI.WebControls.TreeNodeEventArgs) Handles TreeView1.TreeNodePopulate
PopulateSubLevel(CInt(e.Node.Value), e.Node)
End Sub
That is, when the TreeView’s TreeNodePopulate event is raised, PopulateSubLevel is called.
TreeNodePopulate is raised for a TreeNode which is expanded by the user for the first time. Due to PopulateNodesFromClient (TreeView) and PopulateOnDemand (TreeNode) settings, this happens with the client-side callback mechanism which is handled by the ASP.NET Page framework. This means that populating does not involve a postback. And, due to better cross-browser support in ASP.NET 2.0, this also works for other browsers such as Firefox. Note that clicking on the node does cause a postback because I haven’t modified the select action of the populated tree nodes from the defaults.
With this setup, trying our sample page and expanding the nodes, we should get a view like this.
Conclusion
We have set up a working TreeView relatively easily. This approach requires a little bit of code, but shows that it’s not really because of the TreeView control but due to the nature of hierarchical data. Certainly, there’s room for creating a hierarchical data source control to deal with this so that data access wouldn’t require that much code; however, I believe that getting your hands in code is the best way to learn about the technology.