Introduction
Some time ago, my task was to write something like a virtual file system. Of course, I decided to use typed DataSet
s because I had already written a framework to work and update them easily. With this technology, it is very easy to display the content of a folder. Relational DataTable
s are very great tools for this. That’s all right, but when I saw the result - I died! That was not the look and feel that I had mentioned to my client! So I opened Google and started searching for TreeView
with data binding enabled. Of course, I found something: this (Data Binding TreeView in C#) was pretty but that was not the hierarchy in my mind; this (How to fill hierarchical data into a TreeView using base classes and data providers) was pretty too but I didn't understand why the author doesn't like standard binding. Both were not for me! And as a real Ukrainian man, I decided to write my own!
Binding Implementation
First of all, we need a method to fill all the data in the tree view. For this, I store references of the data items in the ArrayList
. This gives me an indicator of the items that are not in the tree. I will iterate through the items until the length of this array becomes 0
. This realization will throw an exception if some of the items cannot find their places in the try. If you need another realization of this (for example, do nothing or store these items on the bottom of the root node), please let me know. I will try to update this article.
ArrayList unsortedNodes = new ArrayList();
for (int i = 0; i < this.listManager.Count; i++)
{
unsortedNodes.Add(this.CreateNode(this.listManager, i));
}
int startCount;
while (unsortedNodes.Count > 0)
{
startCount = unsortedNodes.Count;
for (int i = unsortedNodes.Count-1; i >= 0 ; i--)
{
if (this.TryAddNode((DataTreeViewNode)unsortedNodes[i]))
{
unsortedNodes.RemoveAt(i);
}
}
if (startCount == unsortedNodes.Count)
{
throw new ApplicationException("Tree view confused
when try to make your data hierarchical.");
}
}
private bool TryAddNode(DataTreeViewNode node)
{
if (this.IsIDNull(node.ParentID))
{
this.AddNode(this.Nodes, node);
return true;
}
else
{
if (this.items_Identifiers.ContainsKey(node.ParentID))
{
TreeNode parentNode =
this.items_Identifiers[node.ParentID] as TreeNode;
if (parentNode != null)
{
this.AddNode(parentNode.Nodes, node);
return true;
}
}
}
return false;
}
Respond to External Data Changes
Okay… now we have our tree view filled with all items. Second one that we need is to respond to external data changes. For this, we need to handle the ListChanged
event of the current context.
((IBindingList)this.listManager.List).ListChanged +=
new ListChangedEventHandler(DataTreeView_ListChanged);
Realization of the handle is very simple.
private void DataTreeView_ListChanged(object sender, ListChangedEventArgs e)
{
switch(e.ListChangedType)
{
case ListChangedType.ItemAdded:
break;
case ListChangedType.ItemChanged:
break;
case ListChangedType.ItemMoved:
break;
case ListChangedType.ItemDeleted:
break;
case ListChangedType.Reset:
break;
}
}
Now our control particularly supports data binding. You are able to see data, it will change synchronously with external data.
Selected Node
You can ask what additional functionality is required? - Oh! this is only the start.
So the next point: If you change the data source position, our control will not change it. Currency manager has a PositionChanged
event. We will use it.
this.listManager.PositionChanged +=
new EventHandler(listManager_PositionChanged);
At the start point, we added index for positions and nodes according to them. This gives us an easy way to find a node by its position. So this short code will give us the ability to find the selected node by its position.
DataTreeViewNode node =
this.items_Positions[this.listManager.Position] as DataTreeViewNode;
if (node != null)
{
this.SelectedNode = node;
}
Current Context Position
Now you are not able to use this control as parent to your table. Basically, all that we need is according to the selection of the node, change position of the context. This is not a problem as we store position of the item in each node. Make the AfterSelect
event:
private void DataTreeView_AfterSelect(object sender,
System.Windows.Forms.TreeViewEventArgs e)
{
DataTreeViewNode node = e.Node as DataTreeViewNode;
if (node != null)
{
this.listManager.Position = node.Position;
}
}
Label Editing
Not a problem! Just use AfterLabelEdit
.
private void DataTreeView_AfterLabelEdit(object sender,
System.Windows.Forms.NodeLabelEditEventArgs e)
{
DataTreeViewNode node = e.Node as DataTreeViewNode;
if (node != null)
{
if (this.PrepareValueConvertor()
&& this.valueConverter.IsValid(e.Label)
)
{
this.nameProperty.SetValue(
this.listManager.List[node.Position],
this.valueConverter.ConvertFromString(e.Label)
);
this.listManager.EndCurrentEdit();
return;
}
}
e.CancelEdit = true;
}
Using the Code
As DataSource
you can use any data that, for example, DataGrid
can use. You can use any type of columns to bind. Basically, only the name column is limited to types that can be converted from string
. This applies only when EditLabel
is true
.
In most cases, data must have three columns: Identifier, Name and Identifier of parent row. If you need something like 'FirstName + " " + LastName' as Name field - you can make autocomputed columns in DataSet
.
I am not including image index in binding because I didn't need it. Let me know if you need this functionality. I will update this article.
Bonuses
First bonus is full design time support. Unlike all other data bound trees on “Code Project”, this tree view has all standard designers that, for example, DataGrid
has (of course, with some changes, see Design
namespace). Second one is roundup framework bug with bottom scrollbar in TreeView
(scroll bar is visible all the time, even if it is not needed at all).
That’s all! Enjoy. Visit my blog for the latest news!