Download Project: Treeview2.zip (7.44 kb)
Visual Studio's TreeView Control w/Checkboxes
This will build upon Part 1: Multiple Select. Let us build in a way to save the user's selection and retrieve it. Is it easy? Of course!
Overview: Save, Open and Reset...
The objective in this part, is of course, to Save, Open and Reset the users selections. Also, when opening the user's saved selection, the tree will expand to the level showing all albums selected or not at the minimum and if only some tracks (or just one) of an album are selected, expand the tree branch (albums) to show all tracks selected or not.
Why? Well, this tree does not have the capability of a tri-state checkbox which would show the user that only some of the child-nodes are selected for the branch. So, we need to set a condition where the tree expands one level above the deepest level as a default (at the very least) and expand the deepest level only if it is partially selected. This will show the user everything selected without having some selections hidden due to not having a tri-state checkbox.
.png)
To be able to open and show something like this.
Again to reiterate this Tip: Since this Visual Studio Treeview control was thrown together without much finesse, the total number of items should be kept below ~700. One reason among several... upon rendering, it is a set of nested tables which are uncompressed which helps to make it slow, and to have more than 700 items even with a fast connection, performance degrades exponentially. If your requirements are to exceed 700 items (and mine did) you should consider purchasing a third party control, I chose one from EssentialObjects, cheap, fast, with a multitude of built-in options, plus you can easily leverage dynamic loading.
And not that it matters, my CD collection exceeds well over 700 albums... !!!
Ok, some re-work... it's only to comment one line of code
Where? In the user control for the tree view, to be honest it was a poor choice to define the default XmlDataSource there, anyway it is shown below within the file [ucTree.ascx.cs]:
public partial class ucTree : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
//XmlDataSource1.DataFile = "~/App_Data/Music.xml";
cdT.Attributes.Add("onclick", "OnCheckBoxCheckChanged(event)");
}
and we will move it to page which implements the user control [pgTree.aspx.cs]
public partial class pgTree : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
XmlDataSource xds = cdT1.FindControl("XmlDataSource1") as XmlDataSource;
xds.DataFile = "~/App_Data/Music.xml";
}
}
Yes, it is replaced by 2 lines of code, because the xmldatasource is within the user control, need to find the correct control within the user control...
Page (Html-Markup): pgTree.aspx
Here we are to add 4 controls: a textbox to save and retrieve file settings and 3 buttons to Save, Open and Reset the treeview. Notice that are OnClick events attached to the buttons.
<form id="submitform" name="mainform" method="post" runat="server">
<div id="tabs-1">
<asp:label id="Label1" runat="server" Text="CD's in a TreeView"></asp:label>
<uc1:ucT id="cdT1" runat="server"></uc1:ucT>
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:Button ID="Button1" runat="server" Text="Save" OnClick="Save" />
<asp:Button ID="Button2" runat="server" Text="Open" OnClick="Open"/>
<asp:Button ID="Button3" runat="server" Text="Reset" OnClick="Reset"/>
</div>
</form>
Save: (Server-Side) pgTree.aspx.cs
First, need to check if the user entered a file name (don't need the extension, its set to be xml). If so, then continue, finding the control within the user control, the path to our [App_Data] folder and launch the XmlTextWriter.
After starting the XmlTextWriter we call the function CollectIt(), which is a re-iterative process to traverse the contents of the treeview from the top down. Now this is what we will be saving:
- Renaming the element name and the depth: Level0='All Rock Music', Level1='Band' and so forth...
- Write the attribute "ItemNo" and it's value from the node (our key or the index of unique values).
- And write another attribute "checked" to denote whether the node was selected or not.
// Handle Save: *******************************************************************
protected void Save(object sender, System.EventArgs e)
{
if (TextBox1.Text == "") { MessageBox("Error: Need a file name."); return; }
TreeView Tree = new TreeView();
Tree = cdT1.FindControl("cdT") as TreeView;
TreeNodeCollection treeNodes = Tree.Nodes as TreeNodeCollection;
string tmpPath = Server.MapPath("./App_Data/"); //to get a handle on a virtual folder
string filename = tmpPath + TextBox1.Text + ".XML";
XmlTextWriter tw = new XmlTextWriter(filename, System.Text.Encoding.Unicode);
tw.WriteStartDocument();
collectIt(treeNodes, tw);
tw.WriteEndDocument();
tw.Close();
}
private void collectIt(TreeNodeCollection treeNodes, XmlTextWriter textWriter)
{
foreach (TreeNode node in treeNodes)
{
textWriter.WriteStartElement("Level" + node.Depth.ToString());
textWriter.WriteAttributeString("ItemNo", node.Value);
textWriter.WriteAttributeString("checked", node.Checked.ToString());
if (node.ChildNodes.Count > 0) { collectIt(node.ChildNodes, textWriter); }
textWriter.WriteEndElement();
}
}
// ********************************************************************************
An example of what is saved:
<?xml version="1.0" encoding="utf-16"?>
<Level0 ItemNo="0" checked="False">
<Level1 ItemNo="101.0" checked="False">
<Level2 ItemNo="101.1" checked="True">
<Level3 ItemNo="101.101" checked="True" />
<Level3 ItemNo="101.102" checked="True" />
<Level3 ItemNo="101.103" checked="True" />
etc...
Open: (Server-Side) pgTree.aspx.cs
Here we doing some basic error checking, if the user entered a filename and if it exists. Again we grab the handle to the treeview and open the saved file. Notice that we first collapse all the branches and then expand the branch if it is at the first or second level (Level0-root and Level1-Band) and then only expand the third level (Level2-Album) only if the parent node of Level3-Track (parent node here would be Level2-Album) is unchecked. Similar to the CollectIt() function above, the SetIt() function is also a re-iterative one, transversing the tree from the top down.
// Handle Open: *******************************************************************
protected void Open(object sender, System.EventArgs e)
{
if (TextBox1.Text == "") { MessageBox("Error: Need a file name."); return; }
TreeView Tree = new TreeView();
Tree = cdT1.FindControl("cdT") as TreeView;
TreeNodeCollection treeNodes = Tree.Nodes as TreeNodeCollection;
Tree.CollapseAll();
string tmpPath = Server.MapPath("./App_Data/"); //to get a handle on a virtual folder
string filename = tmpPath + TextBox1.Text + ".XML";
if (!File.Exists(filename)) { MessageBox("Error: File does not exist."); return; }
XmlTextReader tr = new XmlTextReader(filename);
using (XmlReader reader = XmlReader.Create(filename))
{
while (reader.Read())
{
// Only detect start elements, going to ignore anything else
if (reader.IsStartElement())
{
setIt(treeNodes, reader.Depth, reader["ItemNo"], reader["checked"]);
}
}
}
}
private void setIt(TreeNodeCollection treeNodes, int x, string strItemNo, string strChecked)
{
foreach (TreeNode node in treeNodes)
{
if (node.Depth == x && node.Value == strItemNo)
{
switch (node.Depth)
{
case 0: node.Expand(); break;
case 1: node.Expand(); break;
default:
if (strChecked == "True" && node.Parent.Checked != true) { node.Parent.Expand(); }
break;
}
node.Checked = System.Convert.ToBoolean(strChecked);
return;
}
if (node.ChildNodes.Count > 0) { setIt(node.ChildNodes, x, strItemNo, strChecked); }
}
}
// ********************************************************************************
Reset: (Server-Side) pgTree.aspx.cs
This function is nearly identical to the open function except we not opening any file. We are just checking all nodes and expanding only the first 2 levels of the tree.
// Handle Reset: ******************************************************************
protected void Reset(object sender, System.EventArgs e)
{
TreeView Tree = new TreeView();
Tree = cdT1.FindControl("cdT") as TreeView;
TreeNodeCollection treeNodes = Tree.Nodes as TreeNodeCollection;
Tree.CollapseAll();
foreach (TreeNode node in treeNodes) { this.CheckNodes(true, node); }
}
private void CheckNodes(bool check, TreeNode node)
{
node.Checked = check;
foreach (TreeNode child in node.ChildNodes)
{
if (node.Depth <= 1) { node.Expand(); }
if (child.ChildNodes.Count >= 0) { CheckNodes(check, child); }
}
}
// ********************************************************************************
Important Note: This sample project does not take into account the additions and/or deletions made to the original treeview (Music.xml). The saved files would then be out of date and the treeview will only reflect the values which were saved and still valid within Music.xml.
Also, we could have just added the 'checked' attribute to Music.xml and saved it with another file name and only show the info of what was saved. But then you still have to find a way to update the listing. The approach taken here is to show you in such a way that you know what is original and what was saved by reviewing the xml files themselves. Oh, make sure to add-in a safeguard so you won't overwrite your original!
We'll leave this up to you to handle, but now you have the basic tools to take it further...
Download Project: Treeview2.zip (7.44 kb)
the WebPepper team