Tuesday, April 25, 2006

ASP.NET : Binding XML Data to TreeView with Checked state of Checkboxes

Often we come across data that is hierarchial in nature.

Let us take a real world example of a profile page of a person that takes the interests of a person. The Interest categories are categorized in a hierarchial structure, with Categories such as Books, Entertainment, Sports, etc. There will be sub categories, for example, for Books category there might be sub categories as Religion, Literature, etc. and Region might have sub sub categories such as Christianity, Hinduism, etc.

The best way such type of Hierarchial data can be shown is through a Treeview control. To bind the entire set of categories, sub categories, sub sub categories, the best way is to bind the TreeView with XML.

In ASP.NET 2.0 we have an XmlDataSource control that serves exactly this purpose. The XMLDataSource can take up any static XML file that contains XML data.

Ex:
<asp:XmlDataSource ID="CategoriesXmlDataSource" runat="server" DataFile="MyXml.xml"></asp:XmlDataSource>

The XmlDataSource also has a XPath property that is used to filter data in the xml.In case the data to be bound comes from database, we can use the Data property of XmlDataSource control to directly bind the xml string from database to the treeview. From the example we have taken on the Categories and Sub categories, we intend to show check boxes on the treeview, and also want the check boxes to be in checked state according to how the profile has been updated for the person. Let us now check on how we can achieve this.

The first step is to write a proper query that gives an xml document as output. This Xml document must basically contain the entire hierarchial structure that must be displayed as a treeview.If SQL Server is your database, you can easily achieve this by using joins and using the "for xml auto" clause in your query.

Now if you wish to bind the checked state of check boxes as well, using the data from database, you must also bring this checked state as another attribute of every node.

Ex: The xml from database must be something like:

<Categories>
<Category ID="1" Name="Books" checked="false">
<SubCategory Id="2" Name="Religon" checked="false">
<SubSubCategory Id="Hinduism" checked="true" />
<SubSubCategory Id="Christianity" checked="false" />
<SubSubCategory Id="Islam" checked="true" />
</SubCategory>
</Category>
</Categories>

The next step is to make this xml from database to be the datasource for XmlDatasource control. To do this, add this declaration in your aspx page:

<asp:XmlDataSource ID="XmlDataSource1" runat="server" XPath="/Categories/*"></asp:XmlDataSource>

The XPath expression filtes out the top root node and displays the rest of the nodes.
Now in the code behind page add this code in Page_load event:

XmlDataSource1.Data = GetXmlData();

The GetXmlData method retuns the entire xml structure as shown above from database.
The final step is to bind the Treeview control with this XmlDataSource. This is done using the <DataBindings> property of the ASP.NET Treeview.

To the <DataBindings> of the Treeview we add the TreeNodeBinding, for each of the different levels in the TreeView.

For this, add the following declaration in your aspx page:

<asp:TreeView runat="server" ID="tvwCategories" ExpandDepth=1 OnTreeNodeDataBound="tvwCategories_TreeNodeDataBound" ShowCheckBoxes="All" >
<DataBindings>
<asp:TreeNodeBinding DataMember="Country" SelectAction=None PopulateOnDemand=true TargetField="checked" ValueField="ID" extField="Name"></asp:TreeNodeBinding>
<asp:TreeNodeBinding DataMember="Geography" PopulateOnDemand=true SelectAction=None TargetField="checked" ValueField="ID" TextField="Name"></asp:TreeNodeBinding>
<asp:TreeNodeBinding DataMember="Region" TargetField="checked" SelectAction=None PopulateOnDemand=true ValueField="ID" TextField="Name"></asp:TreeNodeBinding>
<asp:TreeNodeBinding DataMember="State" TargetField="checked" SelectAction=None PopulateOnDemand=true ValueField="ID" TextField="Name"></asp:TreeNodeBinding> </DataBindings>
</asp:TreeView>

The DataMember property in TreeNodeBinding is used to specify the different XML elements that will be bound to the TreeView.

The ValueField and TextField properties correspond to the Xml attributes that will assign the Text and Value fields to each node in the TreeView.

The TargetField is here used to set the Checked state of the nodes from the corresponding Xml attribute (i.e. checked). (The TargetField in actual usage is for assigning the "target" html attribute, so that any link from the TreeView will open in the target window accordingly.)

The SelectAction=None specifies that the nodes in the TreeView do not have links or any other click action.

Note the method that is called in OnTreeNodeDataBound event. This method will basically set the checked state of the checkboxes.

The following is the code for the event that has to be added in the codebehind page:

protected void tvwCategories_TreeNodeDataBound(object sender, TreeNodeEventArgs e)
{
if (e.Node.Target == Convert.ToString("true"))
{
e.Node.Checked = true;
}
else
{
e.Node.Checked = false;
}
}

The page is now ready to be compiled and browsed. Of course, there are several ways to set the checked state of checkboxes in treeviews. However, using the above method we avoid the unnecessary for/foreach loops that may have to be used for doing the same.

Thanks.

5 comments:

Vikas Gaddu said...
This comment has been removed by a blog administrator.
Daquell said...

This is the only comment here that's not spam.

The article was cool, by the way.

(notice I'm not promoting any fishy offers)

Unknown said...

Hi..
Great article .It solved my problem. Actually i was getting a problem when using XMLDataource with TreeView control.It always displays the root node of the xml.But using the Xpath the problem was solved.

Thanks again.It's really very very nice article

dgen said...

Ok now I see how the TreeVIew has a DataBindings element, how would you do this with a DataList or a Repeater since it does not have a DataBindings element?

I want to bind the XPath and XPathSelect results from the XmlDataSource to my controls in my ItemTemplate.. is this possible?

Unknown said...

Greetings,

I found your post to be extremely helpful. I had also found a way to enhance your solution. Instead of piggy-backing your value in the Target field and then disabling the SelectAction, you can just keep your extra values in the XML and read them directly.

Here's a portion of the XML that I am reading:

[trenode TextField="Accounting" ValueField="/My Reports/Accounting" ImageUrlField="~/Images/16fold.gif" CollapseTree="0"]
[trenode TextField="Permissions" ValueField="Permissions" ImageURLField="" CheckFalue="false" CollapseTree="1"]
[/trenode]


The XML values that I care about are CheckValue and CollapseTree.

I had changed the TreeNodeDataBound event to:

protected void tvwReports_TreeNodeDataBound(object sender, TreeNodeEventArgs e)
{
String CheckValue;
String CollapseTree;

try
{
CheckValue = ((XmlElement)e.Node.DataItem).Attributes.GetNamedItem("CheckValue").Value;
}
catch
{
CheckValue = "false";
}
try
{
CollapseTree = ((XmlElement)e.Node.DataItem).Attributes.GetNamedItem("CollapseTree").Value;
}
catch
{
CollapseTree = "0";
}

if (CheckValue == "true") //(e.Node.Target == Convert.ToString("true"))
{
e.Node.Checked = true;
}
else
{
e.Node.Checked = false;
}
if (CollapseTree == "1")
{
e.Node.Collapse();
}
else
{
e.Node.Expand();
}

}

I had also removed the "TargetField", "SelectAction" and the "PopulateOnDemand" options from the TreeNodeBinding section. They were not needed anymore since I was reading the values directly.

Thank you for your time.

Terry.