TreeView - DataBinding and Drag/Drop in Windows Forms .NET

By Robbe D. Morris

Printer Friendly Version

Robbe Morris
Robbe & Melisa Morris
  Download Source Code
At the time of this writing (June 2005), the standard Windows Forms .NET TreeView control doesn't support the concept of databinding like the ASP.NET TreeView control does.  So, we are left writing our own solutions.  Recently, I was assigned a UI project exploration task regarding the use of databinding with the Windows Forms .NET TreeView control.  This functionality would enable us to quickly develop user-friendly ways to create complex data analysis models for a new application we're designing.  By the time I was done, I had written a custom class to do this and a whole lot more.
 
The following were the initial requirements:
1.Databind a DataSet to the TreeView and save results back to a Microsoft Access database.
2.Design the solution to make it easy to modify the class to support a 3 party TreeView control should we decide to change to it.  It is for this reason that I opted not to create a custom control.  It also makes it easier to apply different binding strategies in the future.
3.Encapsulate as much of the binding logic inside the custom solution and hide it from the UI developers.  UI developers should have to write next to nothing for code to keep the TreeView in sync with the DataSet underneath it.
4.Provide a sample for deleting nodes in the TreeView (via right click context menu) and have that deletion automatically reflected in the DataSet.
5.Provide a sample for editing nodes in the TreeView (via right click context menu) and have the .Text property change automatically reflected in the DataSet.
6.Provide a sample for inserting nodes in the TreeView (via right click context menu) and have the new node automatically reflected in the DataSet.
7.Support drag and drop of TreeNode branches within the same TreeView and have the relationships automatically reflected in the DataSet.
8.If the following columns are in the DataTable:  Checked, ForeColor, BackColor, ImageIndex, or SelectedImageIndex, incorporate the following node properties when the TreeNode is created.  If the any of these columns are missing, simply implement the default value for the TreeNode.
 
The following are new features I've opted to add:
1.Support drag and drop of TreeNode branches across multiple TreeView controls and have the relationships automatically reflected in the target DataSet as well as the source DataSet.
2.Provide a sample for accepting and rejecting changes made to the same TreeView control giving the user the ability to undo their changes.
3.Automatically commit changes to the source and target DataSet when a TreeNode branch is dropped on the target TreeView.
 
I opted to use the TreeNode.Tag object to hold a copy of the current DataRow.  This methodology provides a standardized way to access which DataTable a node belongs to and an ability to derive primary key and foreign key data points.  This is key to adjusting the DataRow when changes to the TreeView occur.
With this methodology in mind, some preparation to the DataSet would need to take place prior to passing it to the TreeViewUtil class for processing.  You'll see these preparations in the SampleData class which dynamically builds a sample DataSet and populates it with data.  A primary key for each DataTable would need to be assigned.  The DataRelation class would need to be used in order to establish parent/child relationships.  Not only does this help with loading the tree, it also makes it easy to dynamically derive the proper primary key column and DataRelation foreign key column at runtime.  To ensure that cascading deletes occur during a drag/drop session from one TreeView control to another TreeView control, I created a table constraint with a Rule.Cascade setting.
The TreeViewUtil class does make a few assumptions that you need to be aware of.  It assumes the primary key column for the DataTable is a single column and not a composite primary key (multiple columns make it unique).  It assumes that column named SortOrder exists in the DataTable in order to properly save the position to the database on commit.  It assumes that the first DataRelation it finds in the DataSet is the one that defines the parent/child relationship in the tree.  I think these are assumptions you can live with 99% of the time because your applications would typically do this anyway.
You'll also want to notice that I opted to use System.Guid as the data type for my primary key and foreign key in the DataTable.  This is really only necessary if you plan to support drag/drop across multiple TreeView controls.  In this scenario, you would likely run into all sorts of primary key / foreign key violations when dropping a branch from the source TreeView to the target TreeView if your data type for these keys was an int or a string.
The sample form includes a button which exports the contents of the two sample DataSets to xml files (treeview1.xml and treeview2.xml) to the root folder of the application.  This enables you to view the actual contents of the DataSets at any time.
The last item I'd like to mention is that this class does not currently support multi-table DataSets.  It would render the first DataTable and skip the others.  It also doesn't know how to assign potentially different DataTable structures in this type of relationship.  In particular, how to assign a new child branch schema with a different schema than its parent table.  If our project requires implementation of this feature, I'll update this article with the results of my work.
Feel free to download the sample application above or peruse the a portion of the sample code below.
 


 
C# Form1.cs
  using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.IO;
using System.Xml;
using System.Diagnostics; 
using System.Threading;
using TreeSample;

namespace TreeSample
{
 
  public class Form1 : System.Windows.Forms.Form
  {

    private string DBConStr = "";
    private string AppPath = "";
    private ContextMenu tvSample1Menu = new ContextMenu();
    private ContextMenu tvSample2Menu = new ContextMenu();
    private System.ComponentModel.IContainer components;
    private System.Windows.Forms.Label label1;
    private System.Windows.Forms.TreeView tvSample;
    private System.Windows.Forms.Button button1;
    private System.Windows.Forms.Button button2;
    private System.Windows.Forms.TreeView tvSample2;
    private System.Windows.Forms.Label label2;
    private System.Windows.Forms.Button button3;
    private System.Windows.Forms.Button button4;
    private System.Windows.Forms.Button button5;
    private System.Windows.Forms.Button button6;
    private System.Windows.Forms.ImageList imageList1;
 

    private void Form1_Load(object sender, System.EventArgs e)
    {

      UI.Hourglass(true);
 

      try
      {

        AppPath =  UI.GetAppPath(); 

        DBConStr = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" + AppPath + "sample.mdb";
	 
        tvSample1Menu.MenuItems.Add("Insert",
                                    new EventHandler(tvSample1RightClickInsert));

        tvSample1Menu.MenuItems.Add("Edit",
                                    new EventHandler(tvSample1RightClickEdit));

        tvSample1Menu.MenuItems.Add("Nudge Up",
                                    new EventHandler(tvSample1RightClickNudgeUp));

        tvSample1Menu.MenuItems.Add("Nudge Down",
                                    new EventHandler(tvSample1RightClickNudgeDown));

        tvSample1Menu.MenuItems.Add("Delete",
                                    new EventHandler(tvSample1RightClickDelete));  

        tvSample2Menu.MenuItems.Add("Insert",
                                    new EventHandler(tvSample2RightClickInsert)); 

        tvSample2Menu.MenuItems.Add("Edit",
                                    new EventHandler(tvSample2RightClickEdit));

        tvSample2Menu.MenuItems.Add("Nudge Up",
                                    new EventHandler(tvSample2RightClickNudgeUp));

        tvSample2Menu.MenuItems.Add("Nudge Down",
                                    new EventHandler(tvSample2RightClickNudgeDown));

       	tvSample2Menu.MenuItems.Add("Delete",
                                    new EventHandler(tvSample2RightClickDelete));
        
        LoadAllTrees();

        tvSample.AllowDrop = true;
        tvSample2.AllowDrop = true;
       
       }
       catch (Exception err) {  UI.Hourglass(false); UI.ShowError(err.Message); }
       finally { UI.Hourglass(false); }
      }


      private void LoadAllTrees()
      {

        try
        {        
	
          LoadTree(tvSample,Rules.GetHierarchy(DBConStr,1));
          LoadTree(tvSample2,Rules.GetHierarchy(DBConStr,2));
        }
        catch (Exception) { throw; } 
      }


      private void LoadTree(TreeView tv,DataSet ds)
      {

        UI.Hourglass(true);

        try
        {

           TreeViewUtil.LoadFromDataSet(tv,ds,"Description");
        
           if (tv.Nodes.Count > 0)
           {
              tv.Nodes[0].Expand(); 
           }

        }
        catch (Exception) { throw; }
        finally
        {
          UI.Hourglass(false);
        } 
      }
    

 
      private void tvSample1RightClickDelete(object sender, System.EventArgs e)
      {
          
        UI.Hourglass(true);

        try
        {
           TreeViewUtil.DeleteNode(tvSample,true);
        }
        catch (Exception err) { UI.ShowError(err.Message); }
        finally {   UI.Hourglass(false); }
      }
    

      private void tvSample2RightClickDelete(object sender, System.EventArgs e)
      {
          
        UI.Hourglass(true);
	  
        try
        {
           TreeViewUtil.DeleteNode(tvSample2,true);
        }
        catch (Exception err) { UI.ShowError(err.Message); }
        finally {   UI.Hourglass(false); }
      }
	

      private void tvSample1RightClickEdit(object sender, System.EventArgs e)
      {
          
         UI.Hourglass(true);
	  
         try
         {

            TreeNode node = tvSample.SelectedNode;
 
            if (node == null) { return; }

            node.TreeView.LabelEdit = true;

            node.BeginEdit();

         }
         catch (Exception err) { UI.ShowError(err.Message); }
         finally {   UI.Hourglass(false); }
       }
	

       private void tvSample2RightClickEdit(object sender, System.EventArgs e)
       {
          
          UI.Hourglass(true);
	  
          try
          {

             TreeNode node = tvSample2.SelectedNode;
 
             if (node == null) { return; }

             node.TreeView.LabelEdit = true;

             node.BeginEdit();

          }
          catch (Exception err) { UI.ShowError(err.Message); }
          finally {   UI.Hourglass(false); }
       }
	

       private void tvSample1RightClickNudgeUp(object sender, System.EventArgs e)
       {
          
         UI.Hourglass(true);
	       
         try
         {
           TreeViewUtil.NudgeUp(tvSample.SelectedNode);
         }
         catch (Exception err) { UI.ShowError(err.Message); }
         finally {   UI.Hourglass(false); }
       }


       private void tvSample1RightClickNudgeDown(object sender, System.EventArgs e)
       {
          
          UI.Hourglass(true);
	       
          try
          {
             TreeViewUtil.NudgeDown(tvSample.SelectedNode);
          }
          catch (Exception err) { UI.ShowError(err.Message); }
          finally {   UI.Hourglass(false); }
       }
	
    
       private void tvSample2RightClickNudgeUp(object sender, System.EventArgs e)
       {
          
         UI.Hourglass(true);
	       
         try
         {
           TreeViewUtil.NudgeUp(tvSample2.SelectedNode);
         }
         catch (Exception err) { UI.ShowError(err.Message); }
         finally {   UI.Hourglass(false); }
       }
	

       private void tvSample2RightClickNudgeDown(object sender, System.EventArgs e)
       {
          
          UI.Hourglass(true);
	       
          try
          {
             TreeViewUtil.NudgeDown(tvSample2.SelectedNode);
          }
          catch (Exception err) { UI.ShowError(err.Message); }
          finally {   UI.Hourglass(false); }
       }
	

       private void tvSample1RightClickInsert(object sender, System.EventArgs e)
       {
          
          UI.Hourglass(true);
	  
          try
          {

             TreeNode node = tvSample.SelectedNode;
 
             if (node == null) { return; }

             InsertNewNode(node);

          }
          catch (Exception err) { UI.ShowError(err.Message); }
          finally {   UI.Hourglass(false); }
       }
	

       private void tvSample2RightClickInsert(object sender, System.EventArgs e)
       {
          
          UI.Hourglass(true);
	  
          try
          {

            TreeNode node = tvSample2.SelectedNode;
 
            if (node == null) { return; }

            InsertNewNode(node);

          }
          catch (Exception err) { UI.ShowError(err.Message); }
          finally {   UI.Hourglass(false); }
       }
	

      private void InsertNewNode(TreeNode node)
      {
  
         DataRow row = null;
         DataRow ParentRow = null;
         DataTable dt = null;
         int newindex = 0;

         try
         {

             ParentRow = (DataRow)node.Tag; 

             if (ParentRow == null) { return; }

             newindex = int.Parse(ParentRow["SortOrder"].ToString()) + 1;

             dt = ParentRow.Table;

             row = dt.NewRow();
 
             row["ObjectID"] = Guid.NewGuid().ToString();
             row["ObjectTypeID"] = 1;
             row["ModelID"] = int.Parse(ParentRow["ModelID"].ToString());
             row["NodeID"] = Guid.NewGuid().ToString();
             row["ParentNodeID"] = ParentRow[dt.PrimaryKey[0].ColumnName].ToString();
             row["Description"] = "New Node";
             row["ForeColor"] = "#000000";
             row["BackColor"] = "#FFFFFF";
             row["ImageIndex"] = 0;
             row["SelectedImageIndex"] = 1;
             row["Checked"] = true;
             row["ActiveID"] = 1;
             row["NamedRange"] = "";
             row["NodeValue"] = "";
             row["LastUpdateTime"] = DateTime.Now;
             row["SortOrder"] = newindex;

             dt.Rows.Add(row);

             node.Nodes.Add(TreeViewUtil.GetTreeNodeFromDataRow(row,"Description")); 

         }
         catch (Exception) { throw; }

      }
 


    private void EditNode(TreeNode node,string newText)
    {
    
         DataRow row = null;

         try
         {

            if (node == null) { return; }
            
            row = (DataRow)node.Tag;

            if (row == null) { return; }
 
            row["Description"] = newText;

         }
         catch (Exception) { throw; }

    }


    private void button1_Click(object sender, System.EventArgs e)
    {
      LoadAllTrees();
    }


    private void button2_Click(object sender, System.EventArgs e)
    {

      string filename = "";
      DataSet ds;
      DataRow row;
      DataSet compareds;

      try
      {

         UI.Hourglass(true);
 
         // Write out the contents of tvSample to disk

         filename = Path.Combine(AppPath,"treeview1.xml");

         if (File.Exists(filename))  { File.Delete(filename); }

         if (tvSample.Nodes.Count == 0) { return; }

         row = (DataRow)tvSample.Nodes[0].Tag; 

         ds = row.Table.DataSet; 

         compareds = ds.GetChanges(); 

         if (compareds != null)
         {
           compareds.WriteXml(filename,XmlWriteMode.DiffGram);
         }

        // Write out the contents of tvSample2 to disk

        filename = Path.Combine(AppPath,"treeview2.xml");

        if (File.Exists(filename))  { File.Delete(filename); }

        if (tvSample2.Nodes.Count == 0) { return; }

        row = (DataRow)tvSample2.Nodes[0].Tag; 

        ds = row.Table.DataSet; 

        compareds = ds.GetChanges();
 
        if (compareds != null)
        {
	  compareds.WriteXml(filename,XmlWriteMode.DiffGram);
        }

		  
      }
      catch (Exception err) { UI.ShowError(err.Message); }
      finally {   UI.Hourglass(false); }
    }
	


    private void tvSample_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
    {
     
        TreeViewUtil.SetSelectedNodeByPosition(tvSample,e.X,e.Y);

        if (tvSample.SelectedNode == null) { return; }

        if (e.Button == MouseButtons.Right) { return; } 
		 
    }


    private void tvSample_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
    {

       switch (e.Button)
       {

          case MouseButtons.Right:
			    
               tvSample1Menu.Show(tvSample,new Point(e.X,e.Y));                
               return;
 
          default:
               break;
       }
      
    }


    private void tvSample2_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
    {

        TreeViewUtil.SetSelectedNodeByPosition(tvSample2,e.X,e.Y);

        if (tvSample2.SelectedNode == null) { return; }

        if (e.Button == MouseButtons.Right) { return; } 

    }

    private void tvSample2_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
    {

       switch (e.Button)
       {
           case MouseButtons.Right:
			    
                tvSample2Menu.Show(tvSample2,new Point(e.X,e.Y));                
                break;

           default:
                break;
       }
    }
   

    private void tvSample_ItemDrag(object sender, System.Windows.Forms.ItemDragEventArgs e)
    {
       DoDragDrop(e.Item, DragDropEffects.Move);
    }

    private void tvSample_DragEnter(object sender, System.Windows.Forms.DragEventArgs e)
    {         
      TreeViewUtil.DragEnter((TreeView)sender,e);
    }
 
    private void tvSample_DragOver(object sender, System.Windows.Forms.DragEventArgs e)
    {
      TreeViewUtil.DragOver((TreeView)sender,e);
    }

    private void tvSample_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
    {
    
      DataRow row;
      bool dropOnNewControl = false;

      try
      {
       
       UI.Hourglass(true);
	   
       TreeViewUtil.DragDrop((TreeView)sender,e,ref dropOnNewControl);

       if (dropOnNewControl)
       {
          row = (DataRow)tvSample2.Nodes[0].Tag;
          Rules.CommitHierarchy(DBConStr,row.Table.DataSet);
          row = (DataRow)tvSample.Nodes[0].Tag;
          Rules.CommitHierarchy(DBConStr,row.Table.DataSet);
       }
         

       UI.Hourglass(false);

      }
      catch (Exception err) { UI.ShowError(err.Message); } 
      finally { UI.Hourglass(false); }
    }


    private void tvSample2_ItemDrag(object sender, System.Windows.Forms.ItemDragEventArgs e)
    {
 	  DoDragDrop(e.Item, DragDropEffects.Move);
    }

    private void tvSample2_DragEnter(object sender, System.Windows.Forms.DragEventArgs e)
    {         
  	  TreeViewUtil.DragEnter((TreeView)sender,e);
    }
 
    private void tvSample2_DragOver(object sender, System.Windows.Forms.DragEventArgs e)
    {
	  TreeViewUtil.DragOver((TreeView)sender,e);
    }

    private void tvSample2_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
    {
      DataRow row;
      bool dropOnNewControl = false;

      try
      {
       
       UI.Hourglass(true);
	   
       TreeViewUtil.DragDrop((TreeView)sender,e,ref dropOnNewControl);

       if (dropOnNewControl)
       {
          row = (DataRow)tvSample.Nodes[0].Tag;
          Rules.CommitHierarchy(DBConStr,row.Table.DataSet);
          row = (DataRow)tvSample2.Nodes[0].Tag;
          Rules.CommitHierarchy(DBConStr,row.Table.DataSet);
       }
       
       UI.Hourglass(false);
      }
      catch (Exception err) { UI.ShowError(err.Message); } 
      finally { UI.Hourglass(false); }
    }



      private void tvSample_AfterLabelEdit(object sender, System.Windows.Forms.NodeLabelEditEventArgs e)
      {
         try
         {
             if (e.Label.Trim().Length < 1) { e.CancelEdit = true; }  
             EditNode(tvSample.SelectedNode,e.Label);
             tvSample.SelectedNode.EndEdit(false);
             tvSample.LabelEdit = false;
         }
         catch (Exception err) { UI.ShowError(err.Message); }
      }


      private void tvSample2_AfterLabelEdit(object sender, System.Windows.Forms.NodeLabelEditEventArgs e)
      {
         try
         {
             if (e.Label.Trim().Length < 1) { e.CancelEdit = true; }  
             EditNode(tvSample2.SelectedNode,e.Label);
             tvSample2.SelectedNode.EndEdit(false);
             tvSample2.LabelEdit = false;
         }
         catch (Exception err) { UI.ShowError(err.Message); }
      }



    private void button3_Click(object sender, System.EventArgs e)
    {

        DataRow row = null;
        UI.Hourglass(true); 

         try
         {

            if (tvSample.Nodes.Count == 0) { return; }

            row = (DataRow)tvSample.Nodes[0].Tag;

            Rules.CommitHierarchy(DBConStr,row.Table.DataSet);

         }
         catch (Exception err) { UI.ShowError(err.Message); }
         finally {   UI.Hourglass(false); }

    }
 

    private void button4_Click(object sender, System.EventArgs e)
    {
   
        DataRow row = null;
        UI.Hourglass(true);

         try
         {

            if (tvSample.Nodes.Count < 1) { return; }

            row = (DataRow)tvSample.Nodes[0].Tag;

            row.Table.DataSet.RejectChanges(); 

            LoadTree(tvSample,row.Table.DataSet);
            
         }
         catch (Exception err) { UI.ShowError(err.Message); }
         finally {   UI.Hourglass(false); }
    }
    

    private void button6_Click(object sender, System.EventArgs e)
    {

         DataRow row = null;
         UI.Hourglass(true); 

         try
         {

            if (tvSample2.Nodes.Count == 0) { return; }

            row = (DataRow)tvSample2.Nodes[0].Tag;

            Rules.CommitHierarchy(DBConStr,row.Table.DataSet);  
            
         }
         catch (Exception err) { UI.ShowError(err.Message); }
         finally {   UI.Hourglass(false); }
    }


    private void button5_Click(object sender, System.EventArgs e)
    {

         DataRow row = null;
         UI.Hourglass(true);

         try
         {

            if (tvSample2.Nodes.Count < 1) { return; }

            row = (DataRow)tvSample2.Nodes[0].Tag;

            row.Table.DataSet.RejectChanges(); 

            LoadTree(tvSample2,row.Table.DataSet);
            
         }
         catch (Exception err) { UI.ShowError(err.Message); }
         finally {   UI.Hourglass(false); }
    }
   

    private void Form1_Closed(object sender, System.EventArgs e)
    {
          
    }


    private void cmdExit_Click(object sender, System.EventArgs e)
    {
       this.Close();
       Application.Exit(); 
    }

     
  }
}

  
C# TreeViewUtil.cs
using System;
using System.Drawing;
using System.Xml;
using System.Diagnostics; 
using System.Windows.Forms;
using System.Data;

namespace TreeSample
{
 
   public class TreeViewUtil
   {
		 
      private const string NodeObjectName = "System.Windows.Forms.TreeNode";
      private const string CheckedColumnName = "Checked";
      private const string ForeColorColumnName = "ForeColor";
      private const string BackColorColumnName = "BackColor";
      private const string ImageIndexColumnName = "ImageIndex";
      private const string SelectedImageIndexColumnName = "SelectedImageIndex";
      private const string SortOrderColumnName = "SortOrder";


      public static void CheckAllChildNodes(TreeNode treeNode, bool nodeChecked)
      {
			
         foreach(TreeNode node in treeNode.Nodes)
         {

              node.Checked = nodeChecked;

              if(node.Nodes.Count > 0)
              {
                 CheckAllChildNodes(node, nodeChecked);
              }
              if (!node.Checked)
              {
                  node.Collapse();
              }
              else
              {
                  node.ExpandAll();
              }

           }
        }


       public static void CollapseTreeBranch(TreeNode parentNode)
       {
			 
           try
           {
			 
                if (parentNode.Nodes.Count < 1) { return; }  
								
                for(int i=0;i<parentNode.Nodes.Count;i++)
                {
                    parentNode.Nodes[i].Collapse();
                }
				 
           }
           catch (Exception) { throw; }
 
        }
 

        public static void ShowContextMenu(TreeView tv,ContextMenu menu)
        {
            try
            {
              Point pt = new Point(tv.SelectedNode.Bounds.Left,
                                   tv.SelectedNode.Bounds.Bottom);

               menu.Show(tv,pt);
            }
            catch (Exception) { throw; }
        }


        public static void LoadFromDataSet(TreeView tv,DataSet ds,string textColumnName)
        {
 
          TreeNode node;
          DataRow row;

          try
          {
			 
            tv.Nodes.Clear();
			 	
            if (ds.Tables.Count < 1) { return; }
            
            if (ds.Tables[0].Rows.Count < 1) { return; } 

            string fkcolumnname = FindForeignKeyColumnName(ds.Tables[0]);

            row = ds.Tables[0].Rows[0]; 

            tv.BeginUpdate();  

            node = GetTreeNodeFromDataRow(row,textColumnName);

            tv.Nodes.Add(node);

            AddNodeFromDataRow(node,row,textColumnName,FindForeignKeyRelationName(row.Table));

          }
          catch (Exception) { throw; }
          finally
          {
             tv.EndUpdate(); 
             ds.AcceptChanges(); 
          } 
        }



        public static void AddNodeFromDataRow(TreeNode parentNode,DataRow row,
                                              string textColumnName,string dataRelationName)
        {

           try
           {
        
               foreach(DataRow childrow in row.GetChildRows(dataRelationName))
               {

                  parentNode.Nodes.Add(GetTreeNodeFromDataRow(childrow,textColumnName));
				 
                  if (parentNode.TreeView.CheckBoxes)
                  {
                     if (row.Table.Columns.Contains(CheckedColumnName))
                     {
                       parentNode.LastNode.Checked = (bool)row[CheckedColumnName];
                     }
                  }

                  AddNodeFromDataRow(parentNode.LastNode,childrow,textColumnName,dataRelationName);
				    
               }

           }
           catch (Exception) { throw; }
        }


        public static TreeNode GetTreeNodeFromDataRow(DataRow row,string textColumnName)
        {

            TreeNode child = null;
            string fcolor="";
            string bcolor="";
            string imageidx="";
            string selimageidx="";
			 

            try
            {
                              
              if (row.Table.Columns.Contains(ForeColorColumnName))
              {
                fcolor = row[ForeColorColumnName].ToString();
              }
              if (row.Table.Columns.Contains(BackColorColumnName))
              {
                bcolor = row[BackColorColumnName].ToString();
              }
              if (row.Table.Columns.Contains(ImageIndexColumnName))
              {
                imageidx = row[ImageIndexColumnName].ToString();
              }
              if (row.Table.Columns.Contains(SelectedImageIndexColumnName))
              {
                selimageidx = row[SelectedImageIndexColumnName].ToString();
              }
 
              child = new TreeNode();
              child.Text = row[textColumnName].ToString().Trim();

              if (imageidx.Length > 0)
              {
                child.ImageIndex = Convert.ToInt32(imageidx);
              }
              if (selimageidx.Length > 0)
              {
                child.SelectedImageIndex = Convert.ToInt32(selimageidx);
              }
              if (fcolor.Length > 0) 
              {
                child.ForeColor = ColorTranslator.FromHtml(fcolor);
              }
              if (bcolor.Length > 0) 
              {
                child.BackColor = ColorTranslator.FromHtml(bcolor);
              }

              child.Tag = row;
				
           }
           catch (Exception) { throw; }
           return child;
        }


        public static void SetSelectedNodeByPosition(TreeView tv,int mouseX,int mouseY)
        {

          TreeNode node = null;

          try
          {

             Point pt = new Point(mouseX,mouseY);
          
             tv.PointToClient(pt);
          
             node = tv.GetNodeAt(pt);

             tv.SelectedNode = node;

             if (node == null) return;
          
             if (!node.Bounds.Contains(pt)) { return; }

          }
          catch { }
          return;
        }


        public static void DeleteNode(TreeView tv,bool permitRootNodeDeletion)
        {
			 
            DataRow row;

            try
            {

               if (tv.SelectedNode == null) { return; }

               if (tv.SelectedNode == tv.Nodes[0]) 
               {
                 if (!permitRootNodeDeletion)
                 {
                   return;
                 }
                 else
                 {
                  row = (DataRow)tv.SelectedNode.Tag; 
                  row.Delete(); 
                  tv.Nodes.Clear();
                  return;
                 }
               }
	   
                row = (DataRow)tv.SelectedNode.Tag; 
                row.Delete(); 
                tv.SelectedNode.Remove(); 

            }
            catch (Exception) { throw; }
            return;
         }


         public static void DragEnter(TreeView tv,System.Windows.Forms.DragEventArgs e)
         {

            try
            {

               if (e.Data.GetDataPresent(NodeObjectName, true))
               {
                 e.Effect = DragDropEffects.Move;
                 return;
               }
 
               e.Effect = DragDropEffects.None;
			 
            }
            catch (Exception) { throw; }

         }


         public static void DragOver(TreeView tv,System.Windows.Forms.DragEventArgs e)
         {

             try
             {

                if (!e.Data.GetDataPresent(NodeObjectName, true)) { return; }
 
                Point pt = tv.PointToClient(new Point(e.X,e.Y));
 
                TreeNode tgtnode = tv.GetNodeAt(pt); 
       
                if (tv.SelectedNode != tgtnode)
                {
		
                    tv.SelectedNode = tgtnode;
          
                    TreeNode drop = (TreeNode)e.Data.GetData(NodeObjectName);

                    while (tgtnode != null)
                    {

                        if (tgtnode == drop) 
                        {
                           e.Effect = DragDropEffects.None;
                           return;
                        }

                        tgtnode = tgtnode.Parent;
                    }
        
                }

                e.Effect = DragDropEffects.Move;
              }
              catch (Exception) { throw; }

          }


          public static void DragDrop(TreeView tv,System.Windows.Forms.DragEventArgs e,
                                     ref bool dropOnNewControl)
         {
    
            DataRow row;
            DataRow NewParentRow;
            DataTable dt;

            try
            {

              dropOnNewControl = false;

              if (!e.Data.GetDataPresent(NodeObjectName,true)) { return; }
        
              TreeNode drop = (TreeNode)e.Data.GetData(NodeObjectName);

              TreeNode tgtnode = tv.SelectedNode;

              if (drop == drop.TreeView.Nodes[0]) 
              {
                  return;
              }

              if (drop == tgtnode)
              {
                return;
              }

              NewParentRow = (DataRow)tgtnode.Tag;
              
              dt = NewParentRow.Table;

              row = (DataRow)drop.Tag;
               
              // Is this the same control?

              if (tgtnode.TreeView == drop.TreeView)
              {
                // If same control, just change the parent key;
                row[FindForeignKeyColumnName(dt)] = NewParentRow[dt.PrimaryKey[0].ColumnName];
              }
              else
              {
                dropOnNewControl = true;
                // If different control, we must copy the branch of DataRows
                // to the target node's DataRow DataTable.
                SetNewDataRowReferencesForNode(drop,row,NewParentRow,true);
                row.Delete(); 
              }
 
              // This removes the TreeNodes from the TreeView but
              // doesn't destroy our drop DataRows.

              drop.Remove();

              if (tgtnode == null)
              {
                 tv.Nodes.Add(drop);
              }
              else
              { 
                 tgtnode.Nodes.Add(drop);
              }
        
              ReOrderSiblings(drop);

              drop.EnsureVisible();

              tv.SelectedNode = drop;
              
           } 
           catch (Exception) 
           {
                throw;
           }
       }


        private static void SetNewDataRowReferencesForNode(TreeNode node,DataRow oldRow,
                                         DataRow newParentRow,bool resetThisParentID)
        {

           DataRow NewRow = null;
           DataTable dt;
          
           try
           {

             dt = newParentRow.Table;

             NewRow = CopyRowToNewTable(oldRow,dt);
             
             if (resetThisParentID)
             {
               NewRow[FindForeignKeyColumnName(dt)] = newParentRow[dt.PrimaryKey[0].ColumnName];
             }

             dt.Rows.Add(NewRow);

             node.Tag =  NewRow;

             foreach(TreeNode childnode in node.Nodes)
             {
               SetNewDataRowReferencesForNode(childnode,(DataRow)childnode.Tag,newParentRow,false);
             }

           } 
          catch (Exception) { throw; }

        }


        private static DataRow CopyRowToNewTable(DataRow oldRow,DataTable newParentTable)
        {
    
           DataRow NewRow = null;
 
           try
           {
              
             NewRow = newParentTable.NewRow(); 
             NewRow.ItemArray = oldRow.ItemArray; 
           
           } 
           catch (Exception) { throw; }
           return NewRow;
        }


        public static string FindForeignKeyColumnName(DataTable dt)
        {

           string Ret = "";

           try
           {

              DataSet ds = dt.DataSet;

              DataRelation rel = ds.Relations[0];

              Ret = rel.ChildColumns[0].ColumnName; 

           }
           catch (Exception) { throw; }
           return Ret;
        }


        public static string FindForeignKeyRelationName(DataTable dt)
        {

           string Ret = "";

           try
           {

              DataSet ds = dt.DataSet;

              DataRelation rel = ds.Relations[0];

              Ret = rel.RelationName; 

           }
           catch (Exception) { throw; }
           return Ret;
        }


        public static void NudgeUp(TreeNode node)
        {
           int NewIndex = 0;
           TreeNode NodeClone = null;

           try
           {
  
              if (node == null) { return; }

              if (node.Index == 0) { return; }

              NewIndex = node.Index - 1;
               
              NodeClone = (TreeNode)node.Clone();

              node.Parent.Nodes.Insert(NewIndex,NodeClone); 

              node.Parent.Nodes.Remove(node);

              ReOrderSiblings(NodeClone);

              NodeClone.TreeView.SelectedNode = NodeClone;
             
           }
           catch (Exception) { throw; }
           return;
        }

        public static void NudgeDown(TreeNode node)
        {

           int NewIndex = 0;
           TreeNode NodeClone = null;

           try
           {
  
              if (node == null) { return; }

              NewIndex = node.Index + 2;
               
              if (NewIndex > node.Parent.Nodes.Count) { return; }

              NodeClone = (TreeNode)node.Clone();

              node.Parent.Nodes.Insert(NewIndex,NodeClone); 

              node.Parent.Nodes.Remove(node);

              ReOrderSiblings(NodeClone);

              NodeClone.TreeView.SelectedNode = NodeClone;
             
           }
           catch (Exception) { throw; }
           return;
        }


        private static void SetRowSortOrder(TreeNode node,int newIndex)
        {

           try
           {
 
               DataRow row = (DataRow)node.Tag; 

               row[SortOrderColumnName] = newIndex;
             
           }
           catch (Exception) { throw; }
           return;
        }


        private static void ReOrderSiblings(TreeNode node)
        {

           TreeNode child = null;
          
           try
           {
 
               for(int i=0;i<node.Parent.Nodes.Count;i++)
               {
                  child = node.Parent.Nodes[i]; 
                  SetRowSortOrder(child,i);
               }
              
           }
           catch (Exception) { throw; }
           return;
        }

  }
}
 
      
  
C# Rules.cs
using System;
using System.Data;
using System.Diagnostics; 

namespace TreeSample
{
 
   public class Rules
   {

       public Rules()
       {
 
       }


       public static DataSet GetHierarchy(string connectionString,int modelID)
       {
  
             string sql = "";
             DataSet ds = null;
             
             try
             {

               sql = "select * from hierarchy ";
               sql += "  where modelid=" + modelID.ToString();
               sql += "  order by parentnodeid,sortorder asc";

               ds = DataAccess.GetDataSet(connectionString,sql);

               SetHierarchyRelationships(ds);

             }
             catch (Exception) { throw; }
             return ds;
        }


        public static void SetHierarchyRelationships(DataSet ds)
        {
 
            DataColumn fk;
            DataColumn[] pk = new DataColumn[1];
            ForeignKeyConstraint fkcdelete;
            DataRelation relation;
            string datarelationname = "ParentChild";

             try
             {

                ds.Tables[0].TableName = "Hierarchy";

                pk[0] = ds.Tables[0].Columns["NodeID"];
            
                fk = ds.Tables[0].Columns["ParentNodeID"];

                ds.Tables[0].PrimaryKey = pk;

                fkcdelete = new ForeignKeyConstraint(pk[0],fk);
 
                fkcdelete.DeleteRule = Rule.Cascade; 

                relation = new DataRelation(datarelationname,pk[0],fk,false);

                ds.Tables[0].Constraints.Add(fkcdelete);  

                ds.Tables[0].AcceptChanges();

                ds.Relations.Add(relation);  

            }
             catch (Exception) { throw; }
             return;
        }
 

        public static bool CommitHierarchy(string connectionString,DataSet ds)
        {

           string sql = "";
           bool ret = false;
           int ModelID = 0;
           DataTable dt;

           try
           {

             if (ds.Tables.Count < 1) { return ret; }
             
             dt = ds.Tables[0];

             ModelID = int.Parse(dt.Rows[0]["ModelID"].ToString());

             sql = "select  * from Hierarchy where ModelID = " + ModelID.ToString();

             for(int i=0;i<dt.Rows.Count;i++)
             {

               if (dt.Rows[i].RowState.ToString() == "Deleted") { continue; }

               if (int.Parse(dt.Rows[i]["ModelID"].ToString()) != ModelID)
               {
                 dt.Rows[i]["ModelID"] = ModelID;
               }

             }

             DataAccess.CommitToDataBase(connectionString,ds,sql);

             ret = true;

           }
           catch (Exception) 
           {
               throw;
           }
           return ret;
        }

    }
} 
      
  
C# DataAccess.cs
using System;
using System.Data;
using System.Data.OleDb;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Collections;     

namespace TreeSample
{
 
   public class DataAccess
   {

      public DataAccess()
      {
 
      }


      public static void CommitToDataBase(string connectionString,DataSet ds,string sql)
      {
   
        try
        {

          if (!ds.HasChanges()) { return; } 
     
          using (OleDbConnection conn = new OleDbConnection())
          {
 
            conn.ConnectionString = connectionString;
            conn.Open(); 
	 
            using(OleDbDataAdapter da = new OleDbDataAdapter(sql,conn))
            {
              OleDbCommandBuilder  b = new OleDbCommandBuilder(da); 
              da.Update(ds.Tables[0]); 
              ds.AcceptChanges();
            }

          }

        }
        catch (Exception) { throw; }

     }


     public static DataSet GetDataSet(string connectionString,string sql)
     {
   
           DataSet ds = new DataSet();

           try
           {
       
              using (OleDbConnection conn = new OleDbConnection())
              {
 
                 conn.ConnectionString = connectionString;
                 conn.Open(); 
				 
                 using(OleDbDataAdapter da = new OleDbDataAdapter(sql,conn))
                 {
                    da.Fill(ds);     
                 }

              }
            }
            catch (Exception) { throw; }
            return ds;
        }
        

  }
} 
      
  

Robbe has been a Microsoft MVP in C# since 2004.  He is also the co-founder of NullSkull.com which provides .NET articles, book reviews, software reviews, and software download and purchase advice.