Changes made to the table in the DataGrid
are shown at the same time at the TreeView
(from demo project).
Introduction
Like so many others, when I needed to use the TreeView
control, I found myself doing a Load
method adapting the data from a DataTable
or a DataView
into the TreeView
, over and over again. Furthermore, I had to maintain the same data in the control if the values changed.
The purpose of this article is to show you how to develop a TreeView
that can be bound to a datasource and can keep track of the changes made in the data, updating the nodes.
Main Objective
- Load the data in the tree structure.
- Keep track of changes in the data, showing them in the control.
- Expose a get/set property like
SelectedValue
besides SelectedNode
, generally you have the value of the node you want to select (but not the node itself!!!).
Class Structure
There are two classes in the project, the TreeNodeBound
class that inherits from TreeNode
and exposes two properties: Value
and ParentValue
; and the TreeViewBound
(the control itself) class that exposes the DataSource
, DisplayMember
, ValueMember
, and ParentMember
properties.
Loading the Data
Let's have a look at the LoadTree
function in the TreeViewBound
class. I used a Hashtable
to place the nodes by ValueMember
, that's where the trick is.
private void LoadTree()
{
if (this._datasource != null)
{
Clear();
foreach (DataRow dr in this._datasource.Rows)
{
TreeNodeBound node =
new TreeNodeBound(dr[this._displayMember].ToString());
node.Value = dr[this._valueMember];
node.ParentValue = dr[this._parentMember];
_nodesByValueMember.Add(node.Value, node);
}
foreach (TreeNodeBound node in _nodesByValueMember.Values)
{
if (node.ParentValue != _rootParentValue)
{
TreeNodeBound parent =
(TreeNodeBound) _nodesByValueMember[node.ParentValue];
parent.Nodes.Add(node);
}
else
{
base.Nodes.Add(node);
}
}
}
}
First, I create the nodes and add it to the Hashtable
. Then, I iterate through the nodes, look for the parent node of the node (by the ValueMember
again) and add it as a child of the parent. If the node has no parent, it's a root node and I have to add it to the root collection.
Because classes in .NET are reference types, the whole tree structure is loaded. By doing this, we avoid doing a while (!finish)
type algorithm, very frequently used in biz.
Keeping Track of Data Changes
For that, I subscribe to the events in the DataSource
property.
value.RowDeleting += new DataRowChangeEventHandler(value_RowDeleting);
value.RowChanged += new DataRowChangeEventHandler(value_RowChanged);
There are four possible changes that are taken care of:
- Adding
- Deleting
- Modifying the
DisplayMember
value of a node.
- Modifying the parent value (which involves removing it from the old parent collection and adding it to the new one).
Getting/Setting the SelectedValue
Both cases are trouble-free because of the use of the TreeNodeBound
class and the Hashtable
.
public object SelectedValue
{
get
{
if (this.SelectedNode != null)
{
return ((TreeNodeBound)this.SelectedNode).Value;
}
else
{
return null;
}
}
set
{
if (value != null)
{
this.SelectedNode =
(TreeNodeBound) _nodesByValueMember[value];
}
}
}
Summary
At first, I came with the idea of a TreeView
control that could be bound to any object that implements IList
or IListSource
like in other controls such as the ComboBox
, but with this type of data sources the control couldn't keep track of changes. So, I cut back some functionality to add another one, which I think, is more useful. I look forward to your thoughts.
History
- October 11th, 2005 - article submitted.