Click here to Skip to main content
Email Password   helpLost your password?

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:

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);
        }
    } 
}
You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
Generalhow to show the ghost image when we leave the treeview?
cpw999cn
22:43 11 Oct '09  
I try to drag files from my ListView to a target folder, but when I leave the ListView, the ghost image disappear.
I want to show the ghost image always even when I leave the form. How to do that?
GeneralTree restrictions
dede_dafreak
1:09 23 Jun '09  
First of all let me thank you. Works great!
If someone could please post here the code to restrict the dragging and dropping for nodes between groups.
The root nodes are groups (ex. group1..groupn) and nodes can be dragged only from a group to another.
Like so:
---group1
| |
| -node1
| |
| -node2
---group2
| |
| -node3
| |
| -node4
| |
| -node5
| |
---group3 for example, to move node5 to group3, but not allow group3 to be moved to other groups
Thanks in advance
Dede
GeneralReording nodes
JimLaVine
17:43 6 Jun '09  
This code works just as stated. I have a treeview with nodes which have a collection of child nodes that are not expandable. I want to be able to drop the dragnode in a different index of the parent node.

By changing the following code
    // Add drag node to drop node
dropNode.Nodes.Add(this.dragNode);
to
dropNode.Parent.Nodes.Add(this.dragNode);

I can get the dragged node to be put in the proper parent, but it adds it to the end of the nodes collection. Is there a way using your code to put the dragged node below the drop node?

Thanks for you work,

Jim
GeneralRe: Reording nodes
thebeard
16:08 2 Jul '09  
I have a similar situation. This worked:
dropNode.Parent.Nodes.Insert(dropNode.Index, dragNode);
In most cases, dropping over the lower half of a node, results in insertion after dropNode, and dropping over the upper half inserts dragNode before dropNode. Dropping on an adjacent node just trades positions, as one would expect.
GeneralGreat Work
Richard Blythe
6:52 18 May '09  
5 from me. I'm building a query control that required treenode drag functionality. I was hoping some quality code was out there so I wouldn't have to start from scratch. Keep the articles coming!

There cannot be a crisis today; my schedule is already full.

GeneralCannot open project (VS05)
FransClasener
11:14 8 Jun '08  
Application looks great!

Cant open source however: when double-clicking solution, get error:

Application for project .....csproj is not installed
Make sure tah application for the project type (.csproj) is installed [OK]

Can anybody please help how to load this?
Thanks!
NewsGreat artical w/update to a small issue when using TreeViewDrawMode.OwnerDrawText
Greg Cadmes
14:44 5 Mar '08  
Greetings,

Inside the treeView1_ItemDrag handler, there is a line of code like so:
gfx.DrawString(dragNode.Text, treeView1.Font, new SolidBrush(this.treeView1.ForeColor), (float)this.treeView1.Indent, 1.0f);

That line fails if the treeView.DrawMode is set to TreeViewDrawMode.OwnerDrawText.
To remediate this small issue, I used the following code, and it seems to work fine now.

TextRenderer.DrawText(gfx, dragNode.Text, treeView1.Font, new Point(this.treeView1.Indent, 1), this.treeView1.ForeColor, TextFormatFlags.GlyphOverhangPadding);

HTH,

Greg
GeneralVery good article. Thank you.
Muaddubby
9:03 20 Feb '08  
As a newbbie to drag-and-drop, I found this very helpful. The addition of the images complicates it a bit for someone who has not had to deal with these events before, so reading your article in combination with the MSDN one (mentioned below) made it easier.

All in all, good job. Thx for the help!
Barebones MSDN article on drag-and-drop: http://support.microsoft.com/kb/307968[^]


QuestionHow can this be extended to enable Drag amnd Drop of multiple nodes. [modified]
vinutha kempanna
8:04 20 Feb '08  
Hi,

How can i support selection of the multiple nodes with ctrl selection and dragging and dropping of the nodes.

Regards
Vinutha

modified on Wednesday, February 20, 2008 2:25 PM

QuestionProblem in MdiChild form
Dejan Stojanovic
23:19 31 Oct '07  
This is a great peace of code, but I have problem when I'm using this code in form which is MdiChild form (opened in Parent mdi form).
The problem is in drawing ghost image. It looks like that the coordinates sent to helper in dragover event are not correct when this code is used on treeview contained in mdi child form.

Can someone help me with this ?

Deki
GeneralSelectedNode trail problem with this code
RageKing
13:36 29 Oct '07  
Hello. This is a great piece of code and I got the ghost image to work properly on my treeview with it. However, I still have one problem afterwards. I want each node in the tree to highlight as the selected node when I pass over it. It is currently doing this, but there is something strange within the ghost image that follows the cursor where it leaves part of the blue highlighted box over nodes that I have dragged over but are no longer selected. It leaves almost a blue trail of partially selected nodes. I have tried a number of things to resolve this, including double buffering the treeview. If I refresh the treeview before each different node is selected, it works better but the problem still remains because part of the last node that the cursor passed over is highlighted. Also, there is flickering if I refresh the treeview this often. Has anyone else had this problem or know of a way to correct it? Thank you.
GeneralRe: SelectedNode trail problem with this code
darkzangel
3:38 14 May '08  
The problem appear only in custom treeview such as a treelistview and can be fixed by adding a Application.DoEvents() after selection change as fallow (Only use the fallow code is you have the problem as explained above):

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

// Get actual drop node
TreeNode dropNode = this.treeView1.GetNodeAt(this.treeView1.PointToClient(new Point(e.X, e.Y)));
if(dropNode == null)
{
e.Effect = DragDropEffects.None;
DragHelper.ImageList_DragMove(formP.X - this.treeView1.Left, formP.Y - this.treeView1.Top);
return;
}

e.Effect = DragDropEffects.Move;

// if mouse is on a new node select it
if(this.tempDropNode != dropNode)
{
DragHelper.ImageList_DragShowNolock(false);
this.treeView1.SelectedNode = dropNode;
DragHelper.ImageList_DragShowNolock(true);
tempDropNode = dropNode;
Application.DoEvents();
}

// Avoid that drop node is child of drag node
TreeNode tmpNode = dropNode;
while(tmpNode.Parent != null)
{
if(tmpNode.Parent == this.dragNode) e.Effect = DragDropEffects.None;
tmpNode = tmpNode.Parent;
}
DragHelper.ImageList_DragMove(formP.X - this.treeView1.Left, formP.Y - this.treeView1.Top);
}

GeneralNodes MouseHover Event .
unitecsoft
22:46 7 Aug '07  
Great article , thanx

I need to show a Message when the mouse passes over each node showing the node name and other details ( as a tooltiptext for each node ) . Nodes does not have independent events as MouseHover or MouseEnter . do you have any clue to do this .

Thanx


UnitecSoft

We Will Either Find A Way Or Make One .
Generalhow to highlights XtraTreelist nodes dragging from Grid
Shantanu Behera
22:27 17 Apr '07  
Do u help me for

how to highlights XtraTreelist nodes after dragging from Grid.


Shantanu
software developer

GeneralHow Can I Use It In VB2005?
ICCI
7:32 3 Apr '07  
title
.
GeneralNice job...
matthias.sitte
1:58 9 Mar '07  
Just the piece of code I needed for my app.

Only problem I encountered was it didn't work. Spending hours on this problem, I finally set the AllowDrop property to true. That's it. Everything works fine now.

Thanks! Smile
QuestionHow can I drag between different windows?
sami ve susu
6:05 24 Dec '06  
Im trying to drag between several windows in my application and i can not get any response?!
any help would be excellent
GeneralPerfect. Thank you
Eugen Wiebe
0:55 22 Nov '06  
Wink
QuestionCan we use drag&drop without using win32 API [modified]
AwaisAhmedKhan
1:04 5 Jun '06  
Can we use drag&drop without using win32 API. I am using C# 2.0, I donot want to use API in my project, so is there any thing in C# to do that kind of thing.

But its a great articleSmile

awais khan

-- modified at 6:05 Monday 5th June, 2006
GeneralGreat Job
Cem Kalyoncu
23:51 28 May '06  
Great job, I was about the make the same; I really need this type of drag drop. Is there any chance that you could make it a usercontrol? If not I might try to make it, if you let me todo that.

May the bug killer be with you...
Cem Kalyoncu

GeneralThere is a small bug ~
justin_wang
0:00 27 Apr '06  
Hey,

First of all, thanks for you execllent work!Smile

I was having a little trouble with "treeView_ItemDrag" operation.
The "imageListDrag" will be faraway from MousePosition if the TreeView's Location is not (0,0).

Instead of this:
// 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;

Try this:
// Compute delta between mouse position and node bounds
int dx = p.X + this.treeView1.Indent - this.dragNode.Bounds.Left - this.treeView1.Location.X;
int dy = p.Y - this.dragNode.Bounds.Top - this.treeView1.Location.Y;

Wink thanks again!



I Love C#/VS2005!
http://justinw.cnblogs.com
AnswerRe: There is a small bug ~
kalelpack
11:24 26 Feb '08  
I had this same issue. The form layout I had was
Panel with TabControl inside, with SplitContainer inside the Tab Control, and Treeview inside the left hand panel in the split container.

I had to change this code
// 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;



To
// Compute delta between mouse position and node bounds
int dx = p.X + this.treeView1.Indent - this.dragNode.Bounds.Left +this.panel1.Location.X+ this.tabControl1.Location.X + this.tabPage1.Left + this.splitContainer1.Left + this.splitContainer1.Panel1.Left - this.treeView1.Location.X;

int dy = p.Y - this.dragNode.Bounds.Top +this.panel1.Location.Y+ this.tabControl1.Top + this.tabPage1.Top + this.splitContainer1.Top +this.splitContainer1.Panel1.Top- this.treeView1.Location.Y;


GeneralScrolling down
boldtbanan
11:12 30 Jan '06  
I noticed that the scrolling down function behaves erratically, often skipping multiple nodes. After experimenting for a while, I determined that this is because the IsVisible property of a node will return true even if a tiny fraction of the node is actually visible, causing the NextVisibleNode property to get a node that may be 1.9 node heights off of the screen. To fix this, I modified the scroll down section to be as follows:

else if(pt.Y > (this.treeView1.Location.Y + this.treeView1.Size.Height - 30))
{
EnsureVisibleWithoutRightScrolling(node);
TreeNode node2 = this.treeView1.GetNodeAt(ptTree);
if (node2 == node)
{
if (node.NextVisibleNode != null)
{
node = node.NextVisibleNode;
node.EnsureVisible();
}
}
}

where ptTree is defined earlier in my code as:

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

In order to place the treeview in a location other than the top left corner. There were a few other modifications I made to my code so the TreeView could float on the form, basically following the comment by Luis below (more modifications are needed than what he mentioned). If anyone is interested, I'll post my modifications here.

Overall this is a great article and has helped me out quite a bit in the project I'm working on. Great job!
GeneralRe: Scrolling down
mmjd
20:40 15 Feb '06  
If anyone is interested, I'll post my modifications here.>>>Please do, thanks.

It doesn't matter who is saying, what matters is "WHAT is she saying"
GeneralRe: Scrolling down
kalelpack
5:43 15 Feb '08  
It would be great if you could post your modifications here as I am having the same issues you are mentioning.


Last Updated 24 Jan 2005 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010