Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Dragging tree nodes in C#

0.00/5 (No votes)
23 Jan 2005 15  
This article shows how to implement an Explorer like treeview drag and drop in C#.

TreeView Drag & Drop

Introduction

During the implementation of a C# application, I was faced with the problem of adding drag and drop functionality to a tree view. So far no problems, but in order to make the whole a bit fancier, I decided to add image dragging too, as it is done by the Windows Explorer when dragging files or directories. Here I got into troubles, since image dragging isn't supported by the .NET controls. Not finding satisfactory code in the Internet (maybe I'm not a good surfer...), I decided to try it by myself. Having fun on it, I improved the code by adding scrolling to the component so that a dragged element can be dropped everywhere on the control.

This article will only briefly describe the basics of drag and drop, as it is quite straightforward to implement and there already are a lot of good articles about it. The aim of the article is to describe how image dragging and automatic scrolling while dragging can be implemented in C#.

TreeView Drag and Drop

In order to allow Drag and Drop on a TreeView, the AllowDrop flag must be set and handlers for the the following events (or some of them) must be implemented:

  • ItemDrag - This event is fired as soon as a drag operation is started. This event is specific for listviews and treeviews. The dragged element is passed as an argument of the event. The handler of this event should contain the DoDragDrop() call that begins the drag and drop operation.
  • DragOver - This event is fired when the user drags over a drag and drop control with the mouse.
  • DragEnter - This event is fired when the user moves the mouse onto the control while dragging an element.
  • DragLeave - This event is fired when the user leaves the control with the mouse while dragging an element.
  • DragDrop - This event is fired when the user releases the mouse over the drop target.
  • GiveFeedback - This event gives feedback about the current drag effect and cursor.

Image dragging

The implementation of image dragging requires functionalities that first of all create a ghost image of the dragging element and then move this image as mouse cursor moves over the TreeView control. Part of the needed functionalities are available in the ImageList implementation of Win32 (WinAPI). In order to call these functions, I wrote the class DragHelper that accesses them via P/Invoke.

public class DragHelper
{
    [DllImport("comctl32.dll")]
    public static extern bool InitCommonControls();

    [DllImport("comctl32.dll", CharSet=CharSet.Auto)]
    public static extern bool ImageList_BeginDrag(
        IntPtr himlTrack, // Handler of the image list containing the image to drag

        int iTrack,       // Index of the image to drag 

        int dxHotspot,    // x-delta between mouse position and drag image

        int dyHotspot     // y-delta between mouse position and drag image

    );

    [DllImport("comctl32.dll", CharSet=CharSet.Auto)]
    public static extern bool ImageList_DragMove(
        int x,   // X-coordinate (relative to the form,

                 // not the treeview) at which to display the drag image.

        int y,   // Y-coordinate (relative to the form,

                 // not the treeview) at which to display the drag image.

    );

    [DllImport("comctl32.dll", CharSet=CharSet.Auto)]
    public static extern void ImageList_EndDrag();

    [DllImport("comctl32.dll", CharSet=CharSet.Auto)]
    public static extern bool ImageList_DragEnter(
        IntPtr hwndLock,  // Handle to the control that owns the drag image.

        int x,            // X-coordinate (relative to the treeview)

                          // at which to display the drag image. 

        int y             // Y-coordinate (relative to the treeview)

                          // at which to display the drag image. 

    );

    [DllImport("comctl32.dll", CharSet=CharSet.Auto)]
    public static extern bool ImageList_DragLeave(
        IntPtr hwndLock  // Handle to the control that owns the drag image.

    );

    [DllImport("comctl32.dll", CharSet=CharSet.Auto)]
    public static extern bool ImageList_DragShowNolock(
        bool fShow       // False to hide, true to show the image

    );

    static DragHelper()
    {
        InitCommonControls();
    }
}

The first thing to do when we start dragging an element is to create the ghost image of the tree node. Help is provided by the function ImageList_BeginDrag which creates for us a ghost image. This function needs as parameter the handler of an ImageList with the image to be made transparent in it. To create the image of the tree node to drag, a new bitmap is created and the icon and the label are drawn in it. At the end of the dragging operation, the ghost image is destroyed by a call to the ImageList_EndDrag function. We do all this in the ItemDrag event handler.

private void treeView_ItemDrag(object sender, 
                  System.Windows.Forms.ItemDragEventArgs e)
{
    // Get drag node and select it

    this.dragNode = (TreeNode)e.Item;
    this.treeView1.SelectedNode = this.dragNode;

    // Reset image list used for drag image

    this.imageListDrag.Images.Clear();
    this.imageListDrag.ImageSize = 
          new Size(this.dragNode.Bounds.Size.Width 
          + this.treeView1.Indent, this.dragNode.Bounds.Height);

    // Create new bitmap

    // This bitmap will contain the tree node image to be dragged

    Bitmap bmp = new Bitmap(this.dragNode.Bounds.Width 
        + this.treeView1.Indent, this.dragNode.Bounds.Height);

    // Get graphics from bitmap

    Graphics gfx = Graphics.FromImage(bmp);

    // Draw node icon into the bitmap

    gfx.DrawImage(this.imageListTreeView.Images[0], 0, 0);

    // Draw node label into bitmap

    gfx.DrawString(this.dragNode.Text,
        this.treeView1.Font,
        new SolidBrush(this.treeView1.ForeColor),
        (float)this.treeView1.Indent, 1.0f);

    // Add bitmap to imagelist

    this.imageListDrag.Images.Add(bmp);

    // Get mouse position in client coordinates

    Point p = this.treeView1.PointToClient(Control.MousePosition);

    // Compute delta between mouse position and node bounds

    int dx = p.X + this.treeView1.Indent - this.dragNode.Bounds.Left;
    int dy = p.Y - this.dragNode.Bounds.Top;

    // Begin dragging image

    if (DragHelper.ImageList_BeginDrag(this.imageListDrag.Handle, 0, dx, dy))
    {
        // Begin dragging

        this.treeView1.DoDragDrop(bmp, DragDropEffects.Move);
        // End dragging image

        DragHelper.ImageList_EndDrag();
    }

}

When the mouse is now moved while a tree node is dragged, the ghost image should follow the mouse cursor. This is done by the function ImageList_DragMove. We implement it in the DragOver event handler.

private void treeView1_DragOver(object sender, 
              System.Windows.Forms.DragEventArgs e)
{
    // Compute drag position and move image

    Point formP = this.PointToClient(new Point(e.X, e.Y));
    DragHelper.ImageList_DragMove(formP.X - this.treeView1.Left, 
                                  formP.Y - this.treeView1.Top);

    ...
}

If we leave the TreeView, the ghost image should disappear, and as soon as we re-enter the control, the image should appear again. This is done by the functions ImageList_DragLeave and ImageList_DragEnter. The function ImageList_DragEnter also locks the window for updates to allow a clean dragging of the image. ImageList_DragLeave respectively releases the update lock. We implement these two functions in the corresponding event handlers (treeView1_DragEnter and treeView1_DragLeave).

While an element is dragged, Windows automatically changes the mouse cursor according to the drag effect (copy, move, none, ...). In our example, we use the DragDropEffects.Move drag effect. The mouse cursor we want to have while dragging the ghost image is the normal pointer cursor. The cursor can be set in the GiveFeedback event handler.

private void treeView1_GiveFeedback(object sender, 
           System.Windows.Forms.GiveFeedbackEventArgs e)
{
    if(e.Effect == DragDropEffects.Move) 
    {
        // Show pointer cursor while dragging

        e.UseDefaultCursors = false;
        this.treeView1.Cursor = Cursors.Default;
    }
    else e.UseDefaultCursors = true;

}

As soon as the user drops the element and thus terminates the dragging operation, the control updates must be unlocked calling the function ImageList_DragLeave. The DoDragDrop() call (see treeView1_ItemDrag) terminates and the ghost image is released with ImageList_EndDrag.

private void treeView1_DragDrop(object sender, 
                  System.Windows.Forms.DragEventArgs e)
{
    // Unlock updates

    DragHelper.ImageList_DragLeave(this.treeView1.Handle);

    ...

    }
}

Scrolling while dragging

Without scrolling, we can not reach each node within a tree while we are dragging an element, unless the tree is entirely visible on the screen. We begin scrolling the control if the mouse cursor reaches the top or the bottom of the TreeView control. The scrolling is achieved through the EnsureVisible() method of the TreeNode class. To scroll up, we get the previous visible node through the property PrevVisibleNode of the TreeNode class, and set it visible. Analogously, we scroll down with the property NextVisibleNode and a call to EnsureVisible(). As soon as we begin dragging a node, a timer is started. At each tick of the timer, the current position of the cursor is checked, and if it is near the upper or lower border of the control, the tree is scrolled. To avoid that the dragging image interferes with the scrolling producing ugly graphical effects, we briefly hide the drag image and unlock the paint updates with the function ImageList_DragShowNolock. That's it!

private void timer_Tick(object sender, EventArgs e)
{
    // get node at mouse position

    Point pt = PointToClient(Control.MousePosition);
    TreeNode node = this.treeView1.GetNodeAt(pt);

    if(node == null) return;

    // if mouse is near to the top, scroll up

    if(pt.Y < 30)
    {
        // set actual node to the upper one

        if (node.PrevVisibleNode!= null) 
        {
            node = node.PrevVisibleNode;

            // hide drag image

            DragHelper.ImageList_DragShowNolock(false);
            // scroll and refresh

            node.EnsureVisible();
            this.treeView1.Refresh();
            // show drag image

            DragHelper.ImageList_DragShowNolock(true);

        }
    }
    // if mouse is near to the bottom, scroll down

    else if(pt.Y > this.treeView1.Size.Height - 30)
    {
        if (node.NextVisibleNode!= null) 
        {
            node = node.NextVisibleNode;

            DragHelper.ImageList_DragShowNolock(false);
            node.EnsureVisible();
            this.treeView1.Refresh();
            DragHelper.ImageList_DragShowNolock(true);
        }
    } 
}

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here