ASP Column

Tree Controls with XSL

George Shepherd

Code download available at:ASPColumn0306.exe(128 KB)

Contents

Tree Controls
TreeView and the Designer
The TreeNode
Node Types
Dynamic TreeViews
TreeViews and XML
Building a Directory Tree
TreeView Events
Node Styles
Conclusion

In my last column I looked at a couple of the Microsoft® Internet Explorer WebControls available from MSDN®—specifically the TabStrip, the MultiView, and the PageView server-side controls. These three controls organize data using tabbed pages (see The ASP Column in the February 2003 issue of MSDN Magazine). This time I'll take a look at the TreeView server-side control for displaying hierarchical data.

Tree Controls

Tree-style controls appear all over the place on desktop applications. For example, the classic view of Windows® Explorer displays directory information using a tree control. Displaying data as a tree with collapsible nodes is useful for drilling down into data that's structured hierarchically.

To view the contents of a directory, click on the plus sign (+) next to the directory node and it expands. There's no reason this kind of user interface can't be provided on a Web page—and that's what the TreeView WebControl does.

To download the TreeView control (and the rest of the Internet Explorer server-side controls), go to Internet Explorer Web Controls Download Packages. Note that these controls are not supported by Microsoft, but full source code is available. Place the controls that you want in the toolbox by selecting Tools | Customize Toolbox in Visual Studio® .NET.

TreeView and the Designer

Like the other Internet Explorer controls, the TreeView includes full designer support. Manipulating the TreeView control from the designer is like most other designer-based controls. The designer offers a visual representation of the control as it might appear on the client browser. The designer also presents a list of properties to tweak for managing the tree control.

To use the TreeView control in your app, just get one from the toolbox and drop it into a form. Once a TreeView control is in the designer, the properties can be easily managed through the property window. Figure 1 shows a static TreeView control as it first appears in the designer.

Figure 1 TreeView on a Form

Figure 1** TreeView on a Form **

By default, the TreeView has no nodes. To make the control work, you'll need to add some, either programmatically or through the designer. The Designer support for the TreeView includes a dialog box for defining node types. Adding node types and nodes is really quite straightforward. Figure 2 shows the source code produced by the designer after you have inserted several nodes into an ASPX page.

Figure 2 Displaying the TreeView

<iewc:TreeView id="TreeView1" style= "Z-INDEX: 101; LEFT: 31px; POSITION: absolute; TOP: 22px" runat="server" Height="148px" Width="292px"> <iewc:TreeNode Text="First Level" Expanded="True"> <iewc:TreeNode Text="Second Level" Expanded="True"> <iewc:TreeNode Text="Third Level"></iewc:TreeNode> <iewc:TreeNode Text="Third Level"></iewc:TreeNode> </iewc:TreeNode> <iewc:TreeNode Text="Second Level"> <iewc:TreeNode Text="Third Level"></iewc:TreeNode> <iewc:TreeNode Text="Third Level"></iewc:TreeNode> </iewc:TreeNode> </iewc:TreeNode> <iewc:TreeNode Text="First Level"> <iewc:TreeNode Text="Second Level"> <iewc:TreeNode Text="Third Level"></iewc:TreeNode> </iewc:TreeNode> </iewc:TreeNode> </iewc:TreeView>

The TreeNode

An empty TreeView by itself is pretty useless—any tree-based structure requires nodes. TreeView nodes are represented by the TreeNode class, which includes several standard and self-explanatory properties such as Text and Checkbox. In addition, TreeNode includes cascading style sheets (CSS) to define styles (I'll look at those later), image URLs for associating graphics with particular nodes (for example, directory nodes versus file nodes), a navigation URL (for redirecting when clicking on a node), and a data string to be associated with a node. The navigation URL is useful for managing hierarchical data that can be represented by URIs.

The TreeNode includes various methods for programmatic manipulation as well. For example, the TreeNode class includes methods for adding new nodes to a tree. I'll explain how that's done when creating a TreeView programmatically.

Node Types

The TreeNode class collects properties for representing an individual node. While it's possible to define each of the nodes with raw data, it's often useful to set up default values for different kinds of nodes. The TreeView lets you create your own TreeNode types for a TreeView. TreeNode types define default properties for TreeNode objects. For example, in setting up a TreeView for viewing a directory structure, you'll probably want to have the file and the directory nodes to be represented by different images. Defining TreeNode types lets you associate default images for the file nodes and one for the directory nodes.

Dynamic TreeViews

While building a TreeView using the designer is straightforward, using only the designer will produce a static TreeView. If you know what your data is going to be like ahead of time, that's fine. However, in most cases you know very little about the data you're representing until run time. For example, representing a directory structure through a TreeView should be done dynamically, as the contents of a directory change frequently. To see how to manage a TreeView dynamically, let's look at a Web-based directory explorer intended for viewing project source code through a browser.

Figure 3 Project Explorer

Figure 3** Project Explorer **

The Project Explorer includes two TreeViews—one for categorizing project types and another for viewing the directory structure for each project. Finally, the application includes a read-only textbox for displaying the contents of source code files. Figure 3 shows the Project Explorer in action.

TreeViews and XML

One way to populate a TreeView at run time is to use XML to represent the hierarchical data. The format of the XML includes a TREENODES container and an arbitrarily deep hierarchy of elements. Figure 4 shows some XML for populating the TreeView of the current project. The top-level available project categories include several project nodes each—and each project node includes a string representing the physical project directory that contains all of the source files.

Figure 4 XML for Populating a TreeView

<TREENODES> <treenode Text="ASP.NET Projects" > <treenode Text="DevelopMentor ASP Class" NodeData="c:\asp.net labs\work" /> <treenode Text="DataGrid example" NodeData="c:\datagrid" /> </treenode> <treenode Text = "WinForms Projects" > <treenode Text="C# Drawing Program" NodeData="c:\csharpdraw" /> <treenode Text="C# Scribble Program" NodeData="c:\csharpscribble" /> </treenode> <treenode Text="C++ Projects"> <treenode Text="MFC Projects" NodeData="c:\samples\samples\vc7\mfc" /> <treenode Text="ATLServer Online Address Book" NodeData="c:\samples\atlserver\onlineaddressbook"/> <treenode Text="Extensibility" NodeData="c:\samples\extensibility\managedcwinformswiz"/> </treenode> </TREENODES>

Once the TreeView nodes are described in XML, they may be bound to the control using normal ASP.NET data binding, as shown in Figure 5. When the TreeView's TreeNodeSrc attribute points to the XML file, invoking DataBind connects the contents of the XML file to the TreeView. When working with the sample, modify the XML file to match directories on your machine.

Figure 5 Populating a TreeView Using XML

void Page_Load(object sender, System.EventArgs e) { if(!IsPostBack) { treeviewAvailableProjects.TreeNodeSrc = "availableprojects.xml"; treeviewAvailableProjects.DataBind(); } // CssCollection defined as a class field // will be used later to define the hover // style for the node. string s = @"color:black;background:white; border:black thin solid;"; HoverCssCollection = new CssCollection(s); }

Building a Directory Tree

The second way to build a TreeView at run time is by adding the nodes programmatically. When the user selects one of the available projects to view, the ASPX page examines the directory and builds a TreeView dynamically. Figure 6 shows how you can build a TreeView to represent a physical directory structure.

Figure 6 Building a TreeView for a Physical Directory

TreeNode AddFileNode(FileInfo fi, TreeNode tnParent) { TreeNode tn = new TreeNode(); tn.Text = fi.Name; tn.NodeData = fi.FullName; tn.HoverStyle= HoverCssCollection; tn.ImageUrl = "File.bmp"; tnParent.Nodes.Add(tn); return tn; } TreeNode AddDirNode(DirectoryInfo di, TreeNode tnParent) { TreeNode tn = new TreeNode(); tn.Text = di.Name; tn.ImageUrl = "Dir.bmp"; tnParent.Nodes.Add(tn); return tn; } void BuildDirectoryTree(DirectoryInfo di, TreeNode tnParent) { try { foreach(FileSystemInfo fsi in di.GetFileSystemInfos()) { Trace.Write("BuildDirectoryTree", di.FullName); if(fsi.GetType() == typeof(FileInfo)) { FileInfo fi = (FileInfo)fsi; TreeNode tn = AddFileNode(fi, tnParent); } else { DirectoryInfo subDi = (DirectoryInfo)fsi; TreeNode tn = AddDirNode(subDi, tnParent); BuildDirectoryTree(subDi, tn); } } } catch(Exception e) { Trace.Warn("Building dir list", e.ToString()); } }

The code in Figure 6 uses the .NET Framework Class Library directory support. The code starts with a top-level DirectoryInfo object representing a directory (like "c:\samples"). BuildDirectoryTree iterates through the directory entries. When a file node is found, BuildDirectoryTree adds a file node to the tree. When it finds an entry representing a directory, it adds a directory node and recursively calls itself to generate the next level of nodes. Notice that AddFileNode and AddDirectoryNode use different images for files and directories and that the AddFileNode attaches the physical file name to the NodeData property of the node. The Project Explorer needs the file name when responding to events generated by the TreeView.

TreeView Events

In reviewing the events generated by the TreeView, you'll see they're very similar to other selection-style controls (like the ListBox, the CheckBox, and the DropDownListBox). For example, there are events indicating that a node is checked and that a selected item index has changed. In addition, TreeView generates events indicating that nodes have been expanded or collapsed.

By default, the TreeView AutoPostBack property is false. This means that when you expand a node, contract a node, check a node, or select a node, no event is generated. When the target browser is an up-level browser, the controls use DHTML to modify the appearance of the page—and the server is none the wiser. When the browser is a down-level browser, events are generated for selection changes, checks, expansions, and contractions. Figure 7 shows code responding to the SelectedIndexChange event for the available project's TreeView, causing the TreeView representing the directory to be built. Notice how the directory accessing code is wrapped in a try block.

Figure 7 Responding to the SelectedIndexChange Event

void treeviewAvailableProjects_SelectedIndexChange( object sender, TreeViewSelectEventArgs e) { DirectoryInfo di; string strDir; string sSelectedNode; this.textboxFileContents.Text = ""; treeviewProjectInfo.Nodes.Clear(); sSelectedNode = treeviewAvailableProjects.SelectedNodeIndex; TreeNode tnAvailProjects = treeviewAvailableProjects.GetNodeFromIndex( sSelectedNode); strDir = tnAvailProjects.NodeData; if(strDir != "") { TreeNode tn; try { tn = new TreeNode(); treeviewProjectInfo.Nodes.Add(tn); treeviewProjectInfo.Nodes[0].Text = strDir; di = new DirectoryInfo(strDir); BuildDirectoryTree(di, treeviewProjectInfo.Nodes[0]); } catch(Exception ex) { Trace.Warn(ex.ToString()); } } }

The TreeView uses a slightly different item-indexing scheme from other selection-style controls such as the ListBox and the DropDownList. When an item is selected from the DropDownList or the ListBox, you can easily retrieve the selected item's integer index and its text name by using the SelectedItemIndex and the SelectedItem.Text properties name directly. The TreeView is a bit more complicated in that the TreeView's SelectedNodeIndex is a string. You can't look up the node directly using an integer. The string index includes integers separated by periods representing the position of the node within the hierarchy. For example, the string "0.1.1.1" represents the highlighted node in the Project Info TreeView (the TreeView on the right-hand side) shown in Figure 3. Each number represents a position within each level. Both levels and nodes are zero-based. Other than the indexing issue, responding to TreeNode events is very much like responding to events generated by other controls.

Node Styles

Finally, the nodes within a TreeView may be customized easily using CSS style attributes. The TreeNode class includes properties for managing the default style (the DefaultStyle), the hover style (the HoverStyle property), and the selected style (SelectedStyle) for each node. You can add the styles manually into the page's code:

<SomeTreeView:treenode Text=" Node with Styles" DefaultStyle="background:white;border:solid 1px;color:black " HoverStyle="color:gray" SelectedStyle= "color:blue;font-name:Arial;font-weight:bold" />

The designer also supports adding CSS styles. In addition, you may add the styles programmatically as shown in Figure 5 and Figure 6 (take a look at Page_Load and AddFileNode, respectively). The AddFileNode method assigns a CssCollection defining the hover style to the file node. When the mouse hovers over the node, a surrounding border appears. The HoverStyle behavior applies only when the target browser is an up-level browser that is capable of supporting DHTML.

Conclusion

Tree-based UI tools have been available to desktop developers for a long time now. For example, MFC includes a Tree class and a TreeView class for managing Internet Explorer and Windows Explorer-style user interfaces. The same sort of tree-based UI is now available to ASP.NET developers. Manipulating the TreeView server-side control is very much like programming any other ASP.NET server-side control. There are a number of properties, methods, and events that are available both programmatically and through the designer.

The TreeView supports hierarchical data representation using the ASP.NET server-side control architecture. If the target browser is an up-level browser smart enough to be using DHTML, the TreeView emits DHTML, and the TreeView nodes interact with the user without having to make round-trips. Down-level browsers work as well; however, the user experiences more postbacks. To see how the TreeView reacts using a down-level browser, set the ClientTarget property of the page to "downlevel."

Send your questions and comments for George to  asp-net@microsoft.com.

George Shepherd writes .NET development tools with SyncFusion (https://www.syncfusion.com) and teaches at DevelopMentor. He is the author of a number of programming books, including Programming with Microsoft Visual C++.NET (Microsoft Press, 2002) and Applied .NET (Addison-Wesley, 2001). George may be reached at georges@syncfusion.com or georges@develop.com.