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
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.
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
public static extern bool InitCommonControls();
public static extern bool ImageList_BeginDrag(
IntPtr himlTrack, int iTrack, int dxHotspot, int dyHotspot );
public static extern bool ImageList_DragMove(
int x, int y, );
public static extern void ImageList_EndDrag();
public static extern bool ImageList_DragEnter(
IntPtr hwndLock, int x, int y );
public static extern bool ImageList_DragLeave(
IntPtr hwndLock );
public static extern bool ImageList_DragShowNolock(
bool fShow );
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,
this.dragNode = (TreeNode)e.Item;
this.treeView1.SelectedNode = this.dragNode;
+ this.treeView1.Indent, this.dragNode.Bounds.Height);
Bitmap bmp = new Bitmap(this.dragNode.Bounds.Width
+ this.treeView1.Indent, this.dragNode.Bounds.Height);
Graphics gfx = Graphics.FromImage(bmp);
gfx.DrawImage(this.imageListTreeView.Images, 0, 0);
Point p = this.treeView1.PointToClient(Control.MousePosition);
int dx = p.X + this.treeView1.Indent - this.dragNode.Bounds.Left;
int dy = p.Y - this.dragNode.Bounds.Top;
if (DragHelper.ImageList_BeginDrag(this.imageListDrag.Handle, 0, dx, dy))
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,
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_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 (
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,
if(e.Effect == DragDropEffects.Move)
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
DoDragDrop() call (see
treeView1_ItemDrag) terminates and the ghost image is released with
private void treeView1_DragDrop(object sender,
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)
Point pt = PointToClient(Control.MousePosition);
TreeNode node = this.treeView1.GetNodeAt(pt);
if(node == null) return;
if(pt.Y < 30)
if (node.PrevVisibleNode!= null)
node = node.PrevVisibleNode;
else if(pt.Y > this.treeView1.Size.Height - 30)
if (node.NextVisibleNode!= null)
node = node.NextVisibleNode;