Introduction
This article presents a simple way to populate a Web Form TreeView control and maintain its state using a recursive query,
list of types, and Session object.
Background
A working knowledge of web development with ASP.NET, C#, and SQL Server is helpful to understand the code.
Using the code
I'm using SQL Server 2008 Express and Visual Web Developer 2010 Express.
First I created a database ItemsDb with a table 'items' holding hierarchical data and a stored procedure SP_ItemTreePopulate.
The procedure uses a recursive query to select data from the 'item' table and sort it in hierarchical order:
with item_tree (item_id, parent_item_id, item_name, hierarchy_id)
as
(
select item_id, parent_item_id, item_name,
REPLICATE('0',7-len(convert(varchar(50), item_id))) + convert(varchar(50), item_id) as hierarchy_id
from dbo.items where parent_item_id is null
UNION ALL
select i.item_id, i.parent_item_id, i.item_name, tv.hierarchy_id +
REPLICATE('0',7-len(convert(varchar(50), i.item_id))) + convert(varchar(50), i.item_id)
from dbo.items as i inner join item_tree as tv on i.parent_item_id = tv.item_id
where i.parent_item_id is not null
)
select * from item_tree order by hierarchy_id asc
When you download the source you can restore the database from the BAK file.
Then I created a new Web Site, added a TreeView
control to the
Default.aspx page, and wrote the following code in the Page Load event:
protected void Page_Load(object sender, EventArgs e)
{
DataTable table = null;
MyTreeView.RootNodeStyle.BackColor = System.Drawing.Color.Transparent;
MyTreeView.NodeStyle.BackColor = System.Drawing.Color.Transparent;
MyTreeView.SelectedNodeStyle.BackColor = System.Drawing.Color.Yellow;
MyTreeView.Nodes.Clear();
TreeNode rootNode = new TreeNode("MyItems");
rootNode.Value = "MyItems";
MyTreeView.Nodes.Add(rootNode);
string spname = SPName.SP_ItemTreePopulate.ToString();
try
{
table = SPManager.GetItemTree(spname);
}
catch
{
table = null;
}
if (table != null)
{
int rowCnt1 = table.Rows.Count;
if (rowCnt1 > 0)
{
List<TreeNode> nodeList = new List<TreeNode>();
for (int j = 0; j < rowCnt1; j++)
{
string item_id = table.Rows[j]["item_id"].ToString();
string parent_item_id = table.Rows[j]["parent_item_id"].ToString();
string item_name = table.Rows[j]["item_name"].ToString();
TreeNode nodeparentitem = null;
TreeNode nodeitem = null;
if (parent_item_id == "")
{
nodeparentitem = new TreeNode("Item: " + item_name);
nodeparentitem.Value = item_id;
rootNode.ChildNodes.Add(nodeparentitem);
nodeList.Add(nodeparentitem);
}
else if (parent_item_id != "")
{
nodeitem = new TreeNode("Item: " + item_name);
nodeitem.Value = item_id;
for (int i = 0; i < nodeList.Count; i++)
{
if (nodeList[i].Value == parent_item_id)
{
nodeList[i].ChildNodes.Add(nodeitem);
nodeList.Add(nodeitem);
}
}
}
}
}
}
MyTreeView.Nodes[0].Expand();
}
The code loops through the query result, creates nodes, and adds them to
List<TreeNode>
. When creating a child node,
the code searches List<TreeNode>
for an appropriate parent because at the current moment the parent is already in the list.
When you run the page you see that TreeView
is populated correctly but on every postback it returns to its original state.
To resolve this problem I used the Session object.
I added the following code to the Page Load event (in bold):
protected void Page_Load(object sender, EventArgs e)
{
DataTable table = null;
MyTreeView.RootNodeStyle.BackColor = System.Drawing.Color.Transparent;
MyTreeView.NodeStyle.BackColor = System.Drawing.Color.Transparent;
MyTreeView.SelectedNodeStyle.BackColor = System.Drawing.Color.Yellow;
MyTreeView.Nodes.Clear();
TreeNode rootNode = new TreeNode("MyItems");
rootNode.Value = "MyItems";
MyTreeView.Nodes.Add(rootNode);
string spname = SPName.SP_ItemTreePopulate.ToString();
try
{
table = SPManager.GetItemTree(spname);
}
catch
{
table = null;
}
if (table != null)
{
int rowCnt1 = table.Rows.Count;
if (rowCnt1 > 0)
{
List<TreeNode> nodeList = new List<TreeNode>();
for (int j = 0; j < rowCnt1; j++)
{
string item_id = table.Rows[j]["item_id"].ToString();
string parent_item_id = table.Rows[j]["parent_item_id"].ToString();
string item_name = table.Rows[j]["item_name"].ToString();
TreeNode nodeparentitem = null;
TreeNode nodeitem = null;
if (parent_item_id == "")
{
nodeparentitem = new TreeNode("Item: " + item_name);
nodeparentitem.Value = item_id;
rootNode.ChildNodes.Add(nodeparentitem);
nodeList.Add(nodeparentitem);
string itemExp = nodeparentitem.Text + nodeparentitem.Value;
if (Session[itemExp] != null)
{
if ((string)(Session[itemExp]) == "expended")
nodeparentitem.Expanded = true;
else if ((string)(Session[itemExp]) == "collapsed")
nodeparentitem.Expanded = false;
}
string itemColor = nodeparentitem.Text + "--" + nodeparentitem.Value;
if (Session[itemColor] != null)
{
if ((string)(Session[itemColor]) == "selected")
{
nodeparentitem.Select();
}
}
}
else if (parent_item_id != "")
{
nodeitem = new TreeNode("Item: " + item_name);
nodeitem.Value = item_id;
for (int i = 0; i < nodeList.Count; i++)
{
if (nodeList[i].Value == parent_item_id)
{
nodeList[i].ChildNodes.Add(nodeitem);
nodeList.Add(nodeitem);
}
}
string itemExp = nodeitem.Text + nodeitem.Value;
if (Session[itemExp] != null)
{
if ((string)(Session[itemExp]) == "expended")
nodeitem.Expanded = true;
else if ((string)(Session[itemExp]) == "collapsed")
nodeitem.Expanded = false;
}
string itemColor = nodeitem.Text + "--" + nodeitem.Value;
if (Session[itemColor] != null)
{
if ((string)(Session[itemColor]) == "selected")
{
nodeitem.Select();
}
}
}
}
}
}
MyTreeView.Nodes[0].Expand();
}
Then I added three more event handlers to the TreeView events:
protected void MyTreeView_SelectedNodeChanged(object sender, EventArgs e)
{
string nodeVal = "";
string nodeText = "";
nodeVal = MyTreeView.SelectedNode.Value;
nodeText = MyTreeView.SelectedNode.Text;
for (int k = 0; k < Session.Keys.Count; k++)
{
if (Session.Keys[k].IndexOf("--") != -1)
{
Session[Session.Keys[k]] = null;
}
}
if (!MyTreeView.Nodes[0].Selected)
MyTreeView.RootNodeStyle.BackColor = System.Drawing.Color.Transparent;
string colSess = nodeText + "--" + nodeVal;
Session[colSess] = "selected";
}
protected void MyTreeView_NodeExpand(object sender, TreeNodeEventArgs e)
{
string nodeTxt = e.Node.Text;
string nodeVal = e.Node.Value;
string expSess = nodeTxt + nodeVal;
Session[expSess] = "expended";
}
protected void MyTreeView_NodeCollapse(object sender, TreeNodeEventArgs e)
{
string nodeTxt = e.Node.Text;
string nodeVal = e.Node.Value;
string expSess = nodeTxt + nodeVal;
Session[expSess] = "collapsed";
}
In these events I save Selected
, Expanded
, and
Collapsed
nodes in the Session object and restore them when the TreeView is being recreated.
And do not forget to add:
onselectednodechanged="MyTreeView_SelectedNodeChanged"
OnTreeNodeExpanded="MyTreeView_NodeExpand"
OnTreeNodeCollapsed="MyTreeView_NodeCollapse"
to the TreeView control in the Default.aspx page.
Now running the page you see that the TreeView maintains its state.
History
- December 15th, 2012: Initial post.