ASP.NET Server Controls: Treeview
[C# -BETA 2]

By Peter A. Bromberg, Ph.D.

Peter Bromberg  

With the release of Visual Studio .NET Beta 2 and "Gold" coming in a matter of a month or so (expected Nov. 2001) the components and controls marketplace has heated up considerably. I've seen .NET controls of all types being released by most of the major vendors - everything from "add-ins" for working with databases, to email and upload controls, "Plug-in" Webservices and Data compression classes for compressing/decompressing data over the wire. One of the more glamorous categories of course in in the Web UI area, where a number of vendors have released entries having to do with manipulation and display of data.



And the fact of the matter is, it's a lot easier to use the .NET Platform to author custom controls because of the ability to derive from and override Base classes. One example can be found on the GotDotNet site from Eilon Lipton, a Microsoft intern who has posted the complete source for his Treeview Control. If you are adventurous and want to look into writing your own custom ASP.NET controls, his submission is a good place to start (Good way to practice being "Sharp" too).

Microsoft has also released ASP.NET Server Control versions of the most popular IE controls, namely Treeview, TabStrip, ToolBar and MultiPage. These are all fully .NET compliant server-side controls. They are also, however, "Browser aware", and if the browser is "uplevel" (IE 5.5 or higher) the associated Client-side HTC behavior for each control is automatically downloaded with the page. Now obviously this makes for some very interesting combinations of both client-side data manipulation as well as autopostback server-side data handling.

You can download the control installation package free at http://msdn.microsoft.com/library/default.asp?url=/workshop/webcontrols/overview/overview.asp and install it on your server or development machine. This includes the assemblies as well as all the client-side HTC behaviors.

CAUTION: Some of this Microsoft documentation is obviously being "rushed out" a bit. If you plan on using the Treeview XML Databinding methods, and wish to apply an XSL Transform to a preexisting or dynamically-generated XML data source, be aware that the documentation illustrates the XSL source attribute of the Treeview as "TreeNodeXslSrc". This is WRONG! The correct attribute name is 'TreeNodeXsltSrc". That's XSLT with a "T". I just about went nuts with this trying to figure out why, even after I went backwards and just used Microsoft's own sample, it didn't work. I knew my Transform worked cause I had tried it out separately. So, hope this saves you some time because this is one of the most powerful features (I'll illustrate it shortly, as well as how to programmatically create a Treeview object.) I haven't yet completed my studies, but I wouldn't be surprised if there are a few additional "glitches". If you're having difficulty with a particular area, three newsgroups I'd recommend where you can get help are on msnews.microsoft.com. They are microsoft.public.dotnet.framework.aspnet , microsoft.public.dotnet.languages.csharp, and microsoft.public.dotnet.xml. Remember when you post to these groups, there are a large number of posts and people of all levels of expertise. The best way to get a response to your question is to think it through beforehand and make sure that your post is both clear and detailed. Also, you want to search thoroughly first to see if there's actually already an answer to your question. Otherwise, you'll either get flamed - or just ignored. Also, don't post binaries like BMP screenshots to a non-binary newsgroup. That's just rude! (I only mention this because there are what appear to be otherwise intelligent people actually doing this!).

Now lets get a quick overview of the Treeview control.

The Treeview in its simplest iteration is declaratively inserted on the page with a custom namespace in a manner very similar to how you would attach a custom IE behavior element:

<%@ Page Language="c#" debug="true"%>
<%@ import namespace="Microsoft.Web.UI.WebControls" %>
<%@ register TagPrefix="myxmltree"
Namespace="Microsoft.Web.UI.WebControls"
Assembly="Microsoft.Web.UI.WebControls" %>

Once the assembly import and tagprefix of your choice are registered (don't forget both the namespace and the Assembly attributes in your @ register directive) then you are ready to place instances of the Treeview declaratively on the page, like this:

<form id="myform" runat="server">
<myxmltree:treeview id="tree1" runat="server" />
</form>

Note that the control must be inside a Form tag in order for the server-side actions to take place. Of course, the above has no nodes declared or content and so would do nothing. You can add Nodes and NodeTypes declaratively when constructing the page, or programmatically at runtime. You can also dynamically alter the content, type and other features of nodes and childnodes The examples provided in the documentation clearly illustrate how to declaratively create content and attributes, so there's no reason for me to go over this ground. What I think is particularly interesting, however, is the ability to do Databinding with an XML source, and even to take an existing XML source in whatever format it exists and apply an XSL Transform to it in order to transform it into a new XML document that fits the spec so the Treeview control will properly render it. That's what I'm going to illustrate next, and the easiest way to do it is with a real - life, useful example.

On our Eggheadcafe.com site we have new articles, code snippets and other resources coming in almost on a daily basis. At the top of our main page we have a marquee control that scrolls a newsfeed. What we do "behind the scenes" is fire off a scheduled script that combines this newsfeed of developer-related content with our own XML file "articles" dynamically to create a new XML document that shows the latest newsfeed items PLUS our most recent content from our site. I wanted to take this "articles.xml" file and be able to use it in a Treeview so that if you clicked on a node, you would be taken to that article.

Now the Treeview control will only render content in a specific format. It expects XML that must look like this:

<TREENODES>
<treenode Text="Hotlinks" >
<treenode Text = "this link" />

...etc. for child nodes within each main treenode.
</treenode>
</TREENODES>

But the "articles.xml" file doesn't conform, and I'm not about to rewrite it just for this purpose (take a look: article.xml ). Fortunately, I don't have to. I can supply both a TreeNodeSrc and a TreeNodeXsltSrc attribute in my TreeView tag, like this:

<myxmltree:treeview id="tree1" runat="server" TreeNodeSrc="http://www.nullskull.com/article.xml" TreeNodeXsltSrc="http://www.nullskull.com/hotlinktemplate.xsl" ChildType="Folder" />

My XSL template looks like this:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>
<xsl:template match="/">
<TREENODES>
<treenode Text="Hotlinks" >
<xsl:for-each select="moreovernews/article">
<xsl:element name="treenode">
<xsl:attribute name="DefaultStyle">background:#CCFF66;border:solid 1px;color:blue;font-size:9pt</xsl:attribute>
<xsl:attribute name="HoverStyle">background:#FF66CC;color:brown;font-name;Arial</xsl:attribute>
<xsl:attribute name="SelectedStyle">color:red;font-name=Tahoma;font-weight:bold-italic</xsl:attribute>
<xsl:attribute name="Text" ><xsl:value-of select="headline_text"/></xsl:attribute>
<xsl:attribute name="NavigateURL" ><xsl:value-of select="url"/></xsl:attribute>
<xsl:attribute name="Target" >_blank</xsl:attribute>
</xsl:element>
</xsl:for-each>
</treenode>
</TREENODES>
</xsl:template>
</xsl:stylesheet>

Note that I create the required <TREENODES> root element inline, create the main "Hotlinks" node, and then simply iterate my document, replacing instances of the "article" element with the required "treenode" element, and adding the attributes DefaultStyle, HoverStyle, SelectedStyle, Text, NavigateURL and Target for each node. The result is that I'm Databinding this control with exactly what it expects. The result looks like this. But that's just the beginning - I can do much more. If I want, I can actually have each node load a separate list of links (as XML) pulled out of the database by having the target URL for each say, "Hotlink Topic" point to an ASP or ASPX page that queries SQL Server and sends the correct XML results back in the Response Stream. For example, Hotlink Topic "Articles" might point to a page "GetHotLinks.aspx?category=Articles".

This page would then issue a SQL Server query such as "Select Hotlink_Title , Hotlink_URL, HotLink_Text from Hotlinks where HotlLink_Category = '" +Request.QueryString("category") + "' FOR XML AUTO, ELEMENTS" and return the resultant XML fragment to the calling page where another XSL stylesheet specified in the TreeNodeXsltSrc attribute of the tag for that particular ChildNode would handle the Transform. The databinding only occurs when a node is expanded, so I'm keeping my database activity down until somebody actually expands a node. In addition, the "GetHotlinks.aspx" page would have an output cache directive like <%@ OutputCache Duration="100" VaryByParam="*" %>. This means that anytime within 100 minutes if the page were called again with the same parameter ("category=Articles") the page would automatically be served from the ASP.NET Output Cache.

So we can see there is a lot of interesting flexibility we gain with these databinding features in the server-side controls. Another thing to remember is these controls are all browser - aware, so I don't have to worry if a Netscape or Opera visitor looks at the page. The controls automatically handle HTML 3.2 rendering for these "downlevel" browsers.

The other main area I want to cover here is that of dynamically rendering the control - programmatically at runtime, rather that declaratively when designing the page. This adds a whole new area of flexibility because we now have the ability to use the entire object model exposed by the base classes and all of their overridden methods in dynamically generating and altering the Treeview object at will. A quick look at Microsoft.Web.UI.Webcontrols.dll in ILDASM show just a small portion of the methods available:

What we'll do next is create a Treeview programmatically during page rendering:

<%@ Page Language="c#" debug="true" %>
<%@ import namespace="Microsoft.Web.UI.WebControls" %>
<%@ import namespace="System.Xml" %>
<% @ import namespace="System.Data" %>
<% @ import namespace="System.Data.SqlClient" %>
<%@ register TagPrefix="mytree"
Namespace="Microsoft.Web.UI.WebControls"
Assembly="Microsoft.Web.UI.WebControls" %>
<script Language="C#" runat="server" debug="false">
private void Page_Load(object sender, System.EventArgs e)
{
doTree();

}
public void doTree(){
TreeNode x = new TreeNode();
// create our main node...
x = new TreeNode();
x.Text = "<B><font face="tahoma" Size=3>Hotlinks </font></B>";
Treeview1.Nodes.AddAt(0, x );
// create the childnodes...
TreeNode xx;
xx = new TreeNode();
xx.Text = "C# - Native SQL Provider Sample Speed Tests";
xx.NavigateUrl = "http://www.nullskull.com/articles/20010919.asp";
xx.ImageUrl = "http://www.nullskull.com/images/save.gif";
Treeview1.Nodes[0].Nodes.Add(xx );

xx = new TreeNode();
xx.Text = "ASP.NET Vs. Classic ASP Performance Tests";
xx.NavigateUrl = "http://www.nullskull.com/articles/20010920.asp";
xx.ImageUrl = "http://www.nullskull.com/images/save.gif";
Treeview1.Nodes[0].Nodes.Add(xx );

xx = new TreeNode();
xx.Text = "Build a C# MS Search WebService with COM Interop";
xx.NavigateUrl = "http://www.nullskull.com/articles/20010916.asp";
xx.ImageUrl = "http://www.nullskull.com/images/save.gif";
Treeview1.Nodes[0].Nodes.Add(xx );
}

</script>
<html>
<Body>
<form id="myform" runat="server">
<mytree:treeview id="Treeview1" runat="server" >
</mytree:treeview>
</form>
</Body>
</html>

And if you'd like to view the result, you can see where we've been going with this. Note that I've only used just a few of the methods and properties here. Once again, there is no reason why the NavigateURL attribute can't be set to some dynamic page generation code that returns records from a database , or we can set the TreeNodeSrc property to do the same. If you would like to use the above as some sort of starting point for experimentation, feel free to use the source files in the ZIP file download at the link below.

Time permitting, and as my "experiments" progress, I'll be posting follow up articles on the ToolBar, TabStrip and MultiPage server controls as well.

Download the code that accompanies this article


Peter Bromberg is an independent consultant specializing in distributed .NET solutionsa Senior Programmer /Analyst at in Orlando and a co-developer of the NullSkull.com developer website. He can be reached at info@eggheadcafe.com