Click here to Skip to main content
Click here to Skip to main content

An ASP.NET AJAX TreeView control with templates

By , 7 Dec 2008
 

TreeView2.png Odyssey TreeView3.png

Introduction

OdcTreeView is a templated ASP.NET AJAX Server Control with hierarchical data binding.

This article covers the following topics:

  • Static nodes
  • Populate on demand
  • IHierarchicalDataSource binding
  • Templates
  • NodeBinding event
  • AJAX server controls
  • JavaScript namespaces, classes, properties, and custom events
  • Custom style

Static Nodes

Like the ASP.NET TreeView, you can either use a data source to retrieve the nodes, or you simply specify them in the ASPX source like this:

<odc:OdcTreeView ID="OdcTreeView1" 
    runat="server" EnableViewState="true"> 
<Nodes>
<odc:OdcTreeNode Text="1">
<odc:OdcTreeNode Text="1.1" ImageUrl="~/ColorHS.png" />
<odc:OdcTreeNode Text="1.2" />
</odc:OdcTreeNode>
<odc:OdcTreeNode Text="2" />
<odc:OdcTreeNode Text="3">
<odc:OdcTreeNode Text="3.1" />
<odc:OdcTreeNode Text="3.2" />
<odc:OdcTreeNode Text="3.3">
<odc:OdcTreeNode Text="3.3.1" />
<odc:OdcTreeNode Text="3.3.2" />
<odc:OdcTreeNode Text="3.3.3" />
<odc:OdcTreeNode Text="3.3.4" ImageUrl="~/ColorHS.png" />
<odc:OdcTreeNode Text="3.3.5" ShowCheckBox="true" />
<odc:OdcTreeNode Text="3.3.6" 
  ShowCheckBox="true" IsChecked="true" />
</odc:OdcTreeNode>
</odc:OdcTreeNode>
<odc:OdcTreeNode Text="4">
<odc:OdcTreeNode Text="4.1" />
<odc:OdcTreeNode Text="4.2" 
  PopulateOnDemand="true" IsExpanded="true" />
<odc:OdcTreeNode Text="4.3" />
<odc:OdcTreeNode Text="4.4" />
<odc:OdcTreeNode Text="4.5" />
</odc:OdcTreeNode>
</Nodes>
</odc:OdcTreeView>

Populate on Demand

In conjunction with static nodes, you can also enable population on demand, which means that you don’t need to declare all possible nodes at once, and add child nodes to a parent node only when the parent expands for the very first time. Therefore, you simply set the PopulateOnDemand property of an OdcTreeNode to true, which causes the OdcTreeView to raise a NodePopulate event. This event can be implemented like so:

protected void OdcTreeView1_NodePopulate(object sender, OdcTreeNodeEventArgs e)
{
    OdcTreeNode node = e.Node;
    node.PopulateOnDemand = false;
    for (int i = 1; i < 6; i++)
    {
        OdcTreeNode sub = new OdcTreeNode();
        sub.Text = node.Text + "." + i.ToString();
        sub.IsExpanded = false;
        sub.PopulateOnDemand = i==3;
        node.ChildNodes.Add(sub);
    }
}

Note that this method also sets the PopulateOnDemand to false, so it will not cause another NodePopulate when the node gets collapsed and later expanded, since it stores it state in the View State together with the populated tree nodes.

Data Binding

Of course, OdcTreeNode also supports data binding with any IHierarchicalDataSource. You can fighter declare a DataSourceID with the name of a DataSource that implements IHierarchicalDataSource, or set the DataSource property to any IHierarchicalDataSource at runtime. Additionally, you need to declare at least one OdcTreeNodeBinding class unless you don’t use a customized node template to bind the embedded controls directly. This class specifies how to bind the properties of a data item with the properties of a OdcTreeNodeItem, or it declares what default values a OdcTeeNodeItem should be assigned to. If you don’t set the OdcTreeNodeBinding.Level to a value, then this binding is used as the template for all nodes. If you specify a level, however, this OdcTreeNodeBinding only affects the nodes that are in this hierarchical level.

This example demonstrates declarative data binding with an XML file:

<odc:OdcTreeView ID="OdcTreeView1" 
  runat="server" DataSourceID="XmlDataSource1">
<TreeNodeBindings>
<odc:OdcTreeNodeBinding Level="0" TextField="Title"/>
<odc:OdcTreeNodeBinding Level="1" TextField="Title" />
<odc:OdcTreeNodeBinding Level="2" TextField="Title" 
  ShowCheckBox="true" IsChecked="true" />
<odc:OdcTreeNodeBinding Level="3" TextField="Value" 
  ShowCheckBox="true" IsChecked="false" />
</TreeNodeBindings>
</odc:OdcTreeView>
<asp:XmlDataSource ID="XmlDataSource1" runat="server" 
  DataFile="~/Xml.xml"></asp:XmlDataSource>

Templates

OdcTreeView knows three different templates:

  • NodeTemplate - a data template for each node.
  • EditNodeTemplate - a data template for the node that is in edit mode, which can be only one.
  • ContextMenuTemplate - a template for a context menu that appears when you right click the mouse on a node (unless you are not working with Opera). If you don’t specify a NodeTemplate or EditNodeTemplate, OdcTreeView generates a default template.

The following example demonstrates how to use templates:

<odc:OdcTreeView ID="treeView" runat="server" AutoPostBack="False" >
<ContextMenuTemplate>
<asp:LinkButton ID="btnOkay" runat="server" CommandName="ok" Text="Okay" /><br />
<asp:LinkButton ID="btnCancel" runat="server" CommandName="cancel" Text="Cancel" />
</ContextMenuTemplate>
<EditNodeTemplate>
<asp:TextBox runat="server" ID="tbText" Text='<%# Bind("Text") %>' />
<asp:LinkButton ID="btnRename" runat="server" CommandName="rename" Text="Rename" />
<asp:LinkButton ID="btnCancel" runat="server" CommandName="cancel" Text="Cancel" />
</EditNodeTemplate>
<NodeTemplate>
<%# Container.Node.Text %><asp:LinkButton 
  Style="padding-left: 4px" ID="btnAdd" runat="server"
CommandName="add" Text="Add" />
<asp:LinkButton ID="btnRemove" runat="server" CommandName="remove" Text="Remove" />
<asp:LinkButton ID="btnEdit" runat="server" CommandName="edit" Text="Edit" />
</NodeTemplate>
</odc:OdcTreeView>

As you can see, there are two ways used to bind the OdcTreeNode.Text property:

  1. <%# Bind("Text") %>
  2. Using Bind() enables you to access the properties of the data bound item, rather than the OdcTreeNode itself, so this means that the node must be bound to an object that has a Text property. If not data bound, Bind() retrieves the properties from the node itself.

  3. <%# Container.Node.Text %>
  4. This is the usage to access the properties of the tree node directly. Since Container is of type OdcTreeNodeContainer that exposes Node, DataItem would be the data bound object.

OdcTreeNodeBinding also offers a NodeTemplate to specify. This enables you to modify the look of nodes of different levels. The following example demonstrates how to apply a button to the root node:

<odc:OdcTreeView ID="OdcTreeView1" runat="server" DataSourceID="XmlDataSource1">
<TreeNodeBindings>
<odc:OdcTreeNodeBinding Level="0" TextField="Title">
<NodeTemplate>
<asp:Button runat="server" Text="this is the root" UseSubmitBehavior="False" />
</NodeTemplate>
</odc:OdcTreeNodeBinding>
<odc:OdcTreeNodeBinding Level="1" TextField="Title" />
<odc:OdcTreeNodeBinding Level="2" TextField="Title" 
   ShowCheckBox="true" IsChecked="true" />
<odc:OdcTreeNodeBinding Level="3" TextField="Value" 
   ShowCheckBox="true" IsChecked="false" />
</TreeNodeBindings>
</odc:OdcTreeView>

NodeBinding Event

It is even possible to attach any OdcTreeNodeBinding of the NodeBindings collection to a node determined in an event named NodeBinding. This works both for data binding and static nodes, or population on demand. Thus, you can vary the template of a node by using any terms you want.

The following example illustrates how to use NodeBinding:

ASPX part:
<odc:OdcTreeView ID="OdcTreeView1" runat="server" EnableViewState="true" Font-Size="9"
Font-Names="Arial" EnableDragDrop="true" AutoPostBack="false" DisableTextSelection="true"
EnableClientExpand="true" AllowNodeEditing="false" ExpandDepth="7" 
onnodebinding="OdcTreeView1_NodeBinding" 
onnodepopulate="OdcTreeView1_NodePopulate">
<TreeNodeBindings>
<odc:OdcTreeNodeBinding Name="Root" ShowCheckBox="true">
<NodeTemplate>
<asp:Label ID="Label2" isText="true" runat="server" 
   Text="<%# Container.Node.Text %>" />
</NodeTemplate>
</odc:OdcTreeNodeBinding>
</TreeNodeBindings>
Code part:
/// <summary>
/// Determine what OdcTreeNodeBinding to apply for for a node.
/// </summary>
protected void OdcTreeView1_NodeBinding(object sender, 
               Odyssey.Web.TreeView.OdcTreeNodeBindingEventArgs e)
{
OdcTreeNode node = e.Node;
// if the node has child nodes, apply a different NodeBinding
// that contains a different node template to the node:
if (node.HasChildNodes) e.Binding = e.Bindings.GetNamedBinding("Root");
}

AJAX Server Control

OdcTreeView is an AJAX Server Control, which means that it requires a ScriptManager on the page. With AJAX, the tree view can perform many operations directly in the browser without requiring any postback. So, it is possible to select a node, and expand and collapse nodes completely inside the browser. It is even possible to edit the text of a node in the browser if you set AllowNodeEditing to true. The OdcTreeView adds a hidden field to the page, and registers it to the client part of the control. The client uses JavaScript to update the changes to the hidden field, and on a postback, the server side reads the value of the hidden field and updates the values to reflect the changes that were made at the client side. However, if you want to have a postback when a node is selected, edited, expanded, or collapsed, then you must set the AutoPostBack to false. Note that a postback always occurs when a node gets expanded that has PopulateOnDemand set to true.

JavaScript Namespaces, Classes, Properties, and Custom Events

The client part of the OdcTreeView contains of a JavaScript class named TreeViewControl, a TreeNode class, and a TreeContextMenuEventArgs class. All classes belong to the Odyssey.Web namespace. I don’t want to go in to the details of how to make JavaScript look like C# having namespaces, classes, interfaces, properties, and events, since there are a lot of good books that capture that functionality. The TreeViewControl is the client part of the AJAX control. It catches HTML events, and performs the required postbacks depending on what event has been raised. You can also specify the client events for the OdcTreeView that execute not at the server side, but at the client side by calling a JavaScript function. The following events are currently available:

  • ClientContextMenuOpening(treeView, e);
  • This event occurs before the context menu opens, and allows you to modify the look or behavior of the context menu, and exposes two parameters:

    • treeView of type Odyssey.Web.TreeViewControl.
    • e of type Odyssey.Web.TreeContextMenuEventArgs. e contains the node for which the context menu should be opened and the HTML element that covers the context menu itself. Since e is derived from CancelEventArgs, you can also cancel to open the context menu.
  • ClientNodeSelectionChanged(node, e);
  • Occurs when a node is selected.

  • ClientNodeExpanded(node,e);
  • Occurs when a node is expanded.

  • ClientNodeCollapsed(node, e);
  • Occurs when a node is collapsed.

  • ClientNodeCheckedChanged(node, e);
  • Occurs when a node has changed its get_checked() state.

  • ClienEditModeChanged(node, e);
  • Occurs when a node has changed its isEditable() state.

The following snippet illustrates how to use the client events and how to retrieve properties from the AJAX classes:

<odc:OdcTreeView ID="OdcTreeView1" runat="server" EnableViewState="true" 
ClientNodeTextChanged="nodeTextChanged"
ClientNodeExpanded = "expanded"
ClientContextMenuOpening="myContextMenu" 
ClientNodeCollapsed="collapsed"
ClientNodeSelectionChanged="nodeSelected"
 


</form>
<script type="text/javascript">
function expanded(node, e) {
if (node.getText()>="4")
  alert("Expanded: "+node.getText());
}


function collapsed(node, e) {
if (node.getText() >= "4")
  alert("Collapsed: " + node.getText());
}
 

function nodeTextChanged(node, e) {
var txt = node.getText();
var e = document.getElementsByName("custom");
e[0].innerHTML = "changed: "+txt;

}

function nodeSelected(node, e) {
var value = node.get_selected();
if (value) {
//node.setText("OK");
var txt = node.getText();
var e = document.getElementsByName("custom");
e[0].innerHTML = "selected: " + txt;
}
}
 
function myContextMenu(tree, e) {
var cm = e.menuElement;
var node = e.node;
var text = node.getText();
if (node.isFirst()) {
e.set_cancel(true);
alert("no menu for the first item!");
}
if (text == "3.3.1")
  cm.style.backgroundColor = 
     "Red"; else cm.style.backgroundColor = "";
}
</script>
</body>
</html>

Custom Style

Unlike the ASP.NET TreeView which uses inline styles to render a table to look like a tree, OdcTreeView uses the <ul> and <li> tags that natively represent a tree view and completely relies on style sheets to manipulate the look. By default, OdcTreeView uses "odcTreeView" as the base style class which is included as an embedded resource, and contains all the necessary styles for rendering, including background images to be attached, like expand and collapse buttons. If you want to customize the style, you simply need to copy the TreeView.css from the source code, rename all .odcTreeView occurrences with your own class name, add a reference to your style sheet to the page, and set the ClassName property of the OdcTreeView to that name. For the sake of smaller HTML code, OdcTreeView uses very short names for class names of sub elements, like class="ul". You might think that this could lead to collisions with other controls that might decide to use the same short name, but since the style sheet always uses the base class in conjunction with the sub class, this risk is eliminated:

.odcTreeView
{
    clear:both;
    vertical-align:top;
    padding:2px;
}


.odcTreeView .ul
{
    list-style-type: none;
    list-style-image: none;
    list-style-position: outside;
    padding: 0px 0px 0px 20px;
    margin:0px;
}

.odcTreeView .Ln
{
    background-image: url('<%=WebResource("Odyssey.Web.TreeView.images.line.gif")%>');
    background-repeat:repeat-y;
    background-position: 0px 0px;
}

The HTML code would look like this:

<div class="odcTreeView" id="OdcTreeView1" EnableClientExpand="true">
<ul class="ul">
<li class="Mid" key="1"><div class="div">
<span class="Collapse" event="collapse"></span>
<span event="click" class="span" id="OdcTreeView1_K1">
<img src="ColorHS.png" style="border-width:0px;" />
<span isText="true">1</span>

Points of Interest

The source code contains how to create an AJAX server control, how to add client properties and client events to the control, how to read from an IHierarchicalDataSource, how to implement a DataTemplate, and many more.

History

This is the initial release.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

About the Author

Thomas Gerber
Software Developer (Senior)
Germany Germany
Member
MCPD
Enterprise Application Developer 3.5
Windows Developer 3.5
.ASP.NET Developer 3.5
.NET 2.0 Windows Developer
.NET 2.0 Web Developer
.NET 2.0 Enterprise Application Developer
 

MCTS
.NET 3.5 Windows Forms Applications
.NET 3.5 ASP.NET Applications
.NET 3.5, ADO.NET Application Development
.NET 3.5 WCF
.NET 3.5 WPF
.NET 3.5 WF
Microsoft SQL Server 2008, Database Development
.NET 2.0 Windows Applications
.NET 2.0 Web Applications
.NET 2.0 Distributed Applications
SQL Server 2005
Sharepoint Services 3.0 Application Development
Windows Vista Client Configuration

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Suggestionproblem with populate on demandmemberjuanes112 Dec '12 - 8:05 
Questionthanks!memberMember 16536695 Nov '12 - 2:46 
GeneralMy vote of 5memberANTON_KA10 Oct '12 - 9:05 
QuestionGreat sample, thanksmembersanthosh196914 Sep '12 - 23:37 
GeneralMy vote of 4memberChristian Amado13 Aug '12 - 11:07 
QuestionContext menu server side post backmemberMark Gearyy27 Jun '12 - 1:23 
AnswerRe: Context menu server side post backmemberMark Gearyy4 Jul '12 - 23:31 
QuestionIHierarchicalDataSource is not workingmemberMember 861439112 Mar '12 - 6:53 
GeneralThanks.memberchkk822 Jan '12 - 20:02 
GeneralMy vote of 5memberAJMAL SHAHZAD17 Nov '11 - 0:11 
BugTreeView not working in TabContainermemberyoungaj15 Nov '11 - 6:55 
GeneralNice Control but coulduse some improvementsmemberjoe audette10 Mar '11 - 2:29 
GeneralMy vote of 5memberAli Al Omairi(Abu AlHassan)27 Jan '11 - 16:09 
QuestionHow to find the Node (not selected node) [modified]memberMember 22941172 Nov '10 - 10:14 
GeneralMy vote of 5memberkoolprasad200320 Aug '10 - 0:05 
GeneralShowCheckbox property not working in NodeBindingEvent.aspx pagemembermr.vipan.adecco30 Sep '09 - 1:59 
GeneralOpera 9 and Firefox 1 / 2memberjrdutton9 Sep '09 - 3:48 
Questionjson support [modified]membercparks27027 Aug '09 - 11:27 
GeneralLoadViewStatememberMember 361909128 Jul '09 - 23:51 
GeneralPrinting of this Tree viewmemberPraveenTItu30 Jun '09 - 20:18 
QuestionTree Drill Down [modified]memberyoungaj7 May '09 - 6:51 
AnswerRe: Tree Drill Downmemberyoungaj21 May '09 - 4:56 
Found the answer using the IsExpanded property which i didn't realize could actually expand the node in question. Here was the final code if anyone else is interested.
 
    Private Sub TreeDrillDown(ByVal CurrNode As OdcTreeNode, ByVal TargetNum As String)
        Dim CurrNodeNum As String = Nothing
        Dim TempArray As Array = Nothing
 
        '-- Isolate Node Number
        TempArray = CurrNode.Text.Split(":")
        CurrNodeNum = TempArray(0)
 
        '-- If Node is found select Node
        If CurrNodeNum = TargetNum Then
            OdcTreeView1.SelectedNodekey = CurrNode.Key
        Else
            '-- If This not the Node we are looking for but we are on the right path
            '-- Expand this node and call the TreeDrillDown Again
            If TargetNum.Contains(CurrNodeNum) Then
                Dim Expanded As Boolean
                Expanded = CurrNode.IsExpanded
                If Expanded = False Then
                    'CurrNode.PopulateOnDemand() = True
                    NodePopulate(CurrNode)
                    CurrNode.IsExpanded = True
                    OdcTreeView1.DataBind()
                    UpdatePanelTreeView.Update()
                End If
                For Each Node As OdcTreeNode In CurrNode.ChildNodes
                    TreeDrillDown(Node, TargetNum)
                Next
            End If
        End If
 
    End Sub

GeneralRe: Tree Drill DownmemberWilco013 Oct '10 - 1:09 
GeneralRetreive control information from nodememberUn1imit22 Apr '09 - 9:20 
QuestionProblem using this control with DataBound in a Master/Content page solution.memberjy_p21 Apr '09 - 9:05 
AnswerRe: Problem using this control with DataBound in a Master/Content page solution.memberSalvor19 Jun '09 - 2:45 
QuestionContextMenu???memberAdamant2214 Apr '09 - 11:55 
QuestionNodeClick event without postback?memberAndyStephens14 Apr '09 - 1:02 
AnswerRe: NodeClick event without postback?memberjy_p21 Apr '09 - 10:00 
QuestionHow to store key information? [modified]memberSven Weiberg23 Sep '09 - 22:48 
QuestionVB Versionmembermasterelectric16 Mar '09 - 5:50 
Questionis it possible to use ImageButton?memberMember 53462523 Feb '09 - 8:17 
AnswerRe: is it possible to use ImageButton?memberThomas Gerber2 Mar '09 - 11:37 
QuestionHow to add this control in to my toolboxmemberMember 407602320 Feb '09 - 0:29 
AnswerRe: How to add this control in to my toolboxmemberThomas Gerber20 Feb '09 - 4:59 
GeneralRe: How to add this control in to my toolboxmemberMember 407602320 Feb '09 - 19:14 
GeneralRe: How to add this control in to my toolboxmemberThomas Gerber21 Feb '09 - 12:55 
QuestionCan ClientContextMenuOpening event support in firefox? [modified]memberiso900006 Feb '09 - 21:46 
AnswerRe: Can ClientContextMenuOpening event support in firefox?memberiso9000012 Feb '09 - 19:12 
AnswerRe: Can ClientContextMenuOpening event support in firefox?memberThomas Gerber13 Feb '09 - 1:02 
GeneralRe: Can ClientContextMenuOpening event support in firefox?memberjrdutton9 Sep '09 - 3:50 
GeneralObject reference not set to an instance of an objectmemberyoungaj29 Jan '09 - 5:55 
GeneralRe: Object reference not set to an instance of an objectmember_Groker3 Feb '09 - 3:26 
AnswerRe: Object reference not set to an instance of an objectmemberThomas Gerber3 Feb '09 - 7:42 
GeneralRe: Object reference not set to an instance of an objectmemberyoungaj4 Feb '09 - 5:48 
QuestionNode Tooltip?membergary_millington27 Jan '09 - 4:57 
AnswerRe: Node Tooltip?memberThomas Gerber29 Jan '09 - 1:20 
Questionwhich framework is getting used.memberkishore buddha raju14 Jan '09 - 20:09 
AnswerRe: which framework is getting used.memberThomas Gerber14 Jan '09 - 23:02 
GeneralEditNodeTemplatememberRammoneX13 Jan '09 - 3:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 7 Dec 2008
Article Copyright 2008 by Thomas Gerber
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid