Introduction

Some time ago, my task was to write something like a virtual file system. Of course, I decided to use typed DataSets because I have 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 DataTables 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();
//This is list of items that still have no place in treefor (int i = 0; i <this.listManager.Count; i++)
{
//Fill this list with all items.
unsortedNodes.Add(this.CreateNode(this.listManager, i));
}
int startCount;
//Iterate until list will not empty.while (unsortedNodes.Count >0)
{
startCount = unsortedNodes.Count;
for (int i = unsortedNodes.Count-1; i >= 0 ; i--)
{
if (this.TryAddNode((DataTreeViewNode)unsortedNodes[i]))
{
//Item found its place.
unsortedNodes.RemoveAt(i);
}
}
if (startCount == unsortedNodes.Count)
{
//Throw if nothing was done, in another way this //will continuous loop.thrownew ApplicationException("Tree view confused
when try to make your data hierarchical.");
}
}
privatebool TryAddNode(DataTreeViewNode node)
{
if (this.IsIDNull(node.ParentID))
{
//If parent is null this mean that this is root node.this.AddNode(this.Nodes, node);
returntrue;
}
else
{
if (this.items_Identifiers.ContainsKey(node.ParentID))
{
//Parent already exists in tree so we can add item to it.
TreeNode parentNode =
this.items_Identifiers[node.ParentID] as TreeNode;
if (parentNode != null)
{
this.AddNode(parentNode.Nodes, node);
returntrue;
}
}
}
//Parent was not found at this point.returnfalse;
}

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.

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.

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:

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).

I've been trying to track down this problem with the tree control that was stopping me from changing the parent node of another node in the tree. I think I may have fixed it but I was wondering if you could just let me know if this would cause any weird problems..

The changedNode contains the old data for the node (so the ParentID is set to -1(my root)). When you call RefreshData though, it actually sets the node to the new values so ParentID becomes 0. I then have a problem in the ChangeParent function because it fails to move the node to the new parent because of this check:

<br/>
if (node.ParentID != dataParentID)<br/>

They are the same because RefreshData changed them. However if I comment out the call to RefreshData(), it leaves the parentID set to its old value and performs the move.

Is this a sensible fix for me to make?

Also, do you know why the tree control receives duplicate events for the same action.? I am finding when debugging that for example, AddNode sends two events to the list, it doesn't cause a problem but I just wondered why.

I am trying to implement drag and drop and I am having problems getting the treenode object that is being dragged..

For some reason it thinks the type is of TreeView when in a drag and drop event, the data should be the TreeNode.. Do you have any ideas why this would happen?

Here's the sample code I am trying to work with..

// It would go into this if block if I set it to DataTreeView...
if(e.Data.GetDataPresent("Chaliy.Windows.Forms.DataTreeViewNode", false))
{
Point pt = ((DataTreeView)sender).PointToClient(new Point(e.X, e.Y));
DataTreeViewNode destinationNode = ((DataTreeView)sender).GetNodeAt(pt) as DataTreeViewNode;
newNode = (DataTreeView)e.Data.GetData("Chaliy.Windows.Forms.DataTreeViewNode");

Things that I am already see:
1) "destinationNode.Nodes.Add((DataTreeViewNode) newNode.Clone());". I am not test this but seems this code will not work (I am sure that I was not implement clone for all data, but possible this is done automatically).

2) Second is that removing of the element possible will remove element from datasource. I can suggest to store ID of the data row not whole tree node.

3) This implementation can not sort. So inserting of the new node will not work properly. I mean that this control use position in the datasource to sort nodes.

In other words I can suggest you to use standard TreeView. Fill nodes from datasourse (you can use code from this one), and then implement drag-n-drop. In this way drag-n-drop will not even try to affect on the datasource.

Thanks for the reply, I had a feeling this wouldn't work. I'd rather continue to use your tree control because my main requirement was having bound data to the treeview control. I think I am going to disable the drag drop ability and have a separate dialog to manage the nodes in my tree to let the user edit, remove and add through the application.

I started implementing your DataTreeView but started running in to problems when deleting items. If I delete an item and then try and add one immediately after, the added one doesnt show. After the delete I need to "reinforce" the new node selected - any ideas how? Plus I had to change the oldindex to newindex to get the delete to work.