Click here to Skip to main content
Click here to Skip to main content

Dragging tree nodes in C#

By , 23 Jan 2005
Rate this:
Please Sign up or sign in to vote.

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

About the Author

Michea77
Web Developer
Switzerland Switzerland
No Biography provided

Comments and Discussions

 
Generalnyc post PinmemberParul4531-May-12 19:32 
GeneralMy vote of 5 PinmemberEricBK12-Apr-12 3:48 
SuggestionNice article PinmemberTadTK8-Apr-12 5:49 
GeneralChange the position of the gost image Pinmembernethsu9-Jan-11 21:13 
GeneralImageList can be used! PinmemberorientalCSharp21-Jun-10 19:56 
GeneralSeperator Bar Pinmemberlewisv19-May-10 11:29 
GeneralProblem if error comes between BeginDrag and EndDrag... Pinmemberunknown161030-Apr-10 1:52 
Questionhow to show the ghost image when we leave the treeview? Pinmembercpw999cn11-Oct-09 21:43 
AnswerRe: how to show the ghost image when we leave the treeview? PinmemberQuazistax26-Mar-10 9:14 
GeneralTree restrictions Pinmemberdede_dafreak23-Jun-09 0:09 
GeneralReording nodes PinmemberJimLaVine6-Jun-09 16:43 
GeneralRe: Reording nodes Pinmemberthebeard2-Jul-09 15:08 
GeneralGreat Work PinmemberRichard Blythe18-May-09 5:52 
GeneralCannot open project (VS05) PinmemberFransClasener8-Jun-08 10:14 
NewsGreat artical w/update to a small issue when using TreeViewDrawMode.OwnerDrawText PinmemberGreg Cadmes5-Mar-08 13:44 
GeneralVery good article. Thank you. PinmemberMuaddubby20-Feb-08 8:03 
QuestionHow can this be extended to enable Drag amnd Drop of multiple nodes. [modified] Pinmembervinutha kempanna20-Feb-08 7:04 
QuestionProblem in MdiChild form PinmemberDejan Stojanovic31-Oct-07 22:19 
GeneralSelectedNode trail problem with this code PinmemberRageKing29-Oct-07 12:36 
GeneralRe: SelectedNode trail problem with this code Pinmemberdarkzangel14-May-08 2:38 
GeneralNodes MouseHover Event . Pinmemberunitecsoft7-Aug-07 21:46 
Questionhow to highlights XtraTreelist nodes dragging from Grid PinmemberShantanu Behera17-Apr-07 21:27 
QuestionHow Can I Use It In VB2005? PinmemberICCI3-Apr-07 6:32 
GeneralNice job... Pinmembermatthias.sitte9-Mar-07 0:58 
QuestionHow can I drag between different windows? Pinmembersami ve susu24-Dec-06 5:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 24 Jan 2005
Article Copyright 2005 by Michea77
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid