Click here to Skip to main content
Licence 
First Posted 31 Jul 2004
Views 209,823
Bookmarked 73 times

Drag and Drop Treeview control

By | 31 Jul 2004 | Article
A treeview that has drag and drop built in.

Sample Image - DragDropTreeview.gif

Introduction

One of the common behaviors developers want to implement with treeviews is drag and drop. For some reason, Microsoft haven't made this entirely easy with the .NET TreeView control (and it hasn't changed in the 2.0 beta either). Whilst it's not really that challenging to do, it makes sense to have a single control that has the behavior built in, instead of cutting and pasting the event each time you want to implement drag and drop in a treeview.

Drag and drop is based around 4 events: OnDragDrop, OnDragEnter, OnDragLeave, OnDragOver, and in the TreeView control, OnItemDrag. I won't bother going over the order in which these events are called or what they do, you can read the MSDN docs for that, or use Google. Suffice to say, these events have all been implemented in this TreeViewDragDrop control.

Features of the control

I built the control for a management application for a website I store code examples on (www.sloppycode.net, shameless plug). Remembering the old adage, programming is 90% design, I wrote down the following features I wanted the control to do:

  • Auto scrolling
  • Target node highlighting when over a node
  • Custom cursor when dragging
  • Custom ghost icon label when dragging
  • Escape key to cancel drag
  • Blocks certain nodes from being dragged via a DragStart event
  • Sanity checks for dragging (no parent into children nodes, target isn't the source)

Some of the above features aren't documented in MSDN, but can be found if you trawl through Google groups and websites. I'll go through how I implemented these core features now.

Auto scrolling

This is done via a WinAPI SendMessage call. The OnDragOver event performs a SendMessage call (277 is the constant value, WM_SCROLL I think, but I haven't checked that). This scrolls the treeview up or down, depending on the mouse position.

Target node highlighting

This is pretty straightforward to perform, you simply change the target node's background and foreground color in the OnDragOver event. The default color for this, based on Windows Explorer, is the system 'Highlight' color for the background color, and 'HighlightText' for the foreground color. I've made these properties in the control so people are free to change them to whatever they like.

Custom cursor when dragging

This is done using the OnGiveFeedback event. The event passes a GiveFeedbackEventArgs class which has a UseDefaultCursors property. We can set this to false if we want to implement a custom cursor, and change it to the cursor we want. We also check if dragging is being performed, and change the cursor back if it's not.

Custom ghost icon

This feature is the nicest one of the control, although will probably be the biggest source of bugs. It copies the Windows Explorer behavior of showing the node that is being dragged, as a 'ghosted' or faded out version, including the label of the node and its icon. I implemented this using a second form, which is moved to the position the mouse is. Via properties in the control, you can change the image that is being shown, and the font of the label. It's a nicer version of the custom cursor mentioned above, although I get the feeling I'm doing it a long way round, and that there's a Windows API call that does it for me already - the problem is I don't know what it is. If anyone knows, please say.

Escape key to cancel drag

This is another standard behavior of Windows Explorer's treeview, where you can hit the escape key to cancel the dragging. The OnKeyUp event is where this is performed, it checks to see if the key is the escape key, and cancels the drag if it is, also returning any dragged over nodes to their original state.

Blocks certain nodes from being dragged

I've added a few custom events into the control to add some small extra event functionality. The main one of these is the DragStart event. This is fired when the drag begins, and allows you to check which node is being dragged, and stop the drag drop event if needs be. This can be useful if, for example, you have a recycle bin in your treeview, and don't want the user to be dragging that around the place.

Sanity checks for dragging

This is built in to stop nodes being dragging inside of themselves, or being dragged onto themselves. It uses the Path property to do the check in the fastest way possible. This does mean that if you have 2 paths the same, then it won't work; you may want to build a derived class if you've got this problem, and do it the long way around, by cycling back up the parent nodes and checking it that way.

That's about it. I've added an over-ridden WndProc event which prevents the treeview's background from being repainted on each update, stopping a flicker. It still flickers a bit when you're holding a node, but this seems to be something with the .NET framework controls. Double buffering has been tried but with no success, any suggestions would be welcome.

Hopefully, it'll be useful to some people.

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

yetanotherchris

Web Developer

United Kingdom United Kingdom

Member

London based C# programmer.
 
I maintain my own pet C# site http://www.sloppycode.net in my spare time.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralProblem when dragging a node from one treeview to another treeview Pinmembermightygirls20:35 8 Feb '09  
GeneralRe: Problem when dragging a node from one treeview to another treeview PinmemberVercas9:04 13 Oct '10  
GeneralGood Start PinmemberAli Rafiee12:35 23 Aug '08  
GeneralBuggy Buggy Buggy PinmemberTim Kock5:07 6 Apr '08  
Generaldrag and drop onto different tree control Pinmembererinselena8:18 3 Jul '07  
GeneralRe: drag and drop onto different tree control PinmemberPatrick de Kleijn10:56 10 Jul '07  
Questiondo i need to download this control? PinmemberICCI7:51 29 Mar '07  
AnswerRe: do i need to download this control? Pinmemberrpallas11:15 4 Mar '08  
GeneralVery buggy! PinmemberSocrates#5:16 3 Nov '06  
QuestionDragging from a listbox PinmemberSebicu22:40 24 Oct '06  
GeneralNodes turn black when dragging over PinmemberMathieu Jacques5:34 12 Oct '06  
QuestionRe: Nodes turn black when dragging over Pinmembererinselena5:28 31 Jan '07  
AnswerRe: Nodes turn black when dragging over PinmemberJason Wieth12:57 12 May '09  
QuestionCancel Dragging Due To Target Node Pinmembercentiipede8:46 6 Apr '06  
Generaldrag drop problem Pinmembermargiex23:00 31 Oct '05  
GeneralEnsureVisible without right-scrolling PinsussGavin McKay17:40 12 Oct '05  
GeneralThe API-function you need for the ghost icons PinPopularmembertschenz6:03 28 Sep '05  
GeneralRe: The API-function you need for the ghost icons - working VB.NET implementation Pinmemberdecoda8:32 10 Jan '07  
CreateDragCursor: creates Cursor with expanded Subnodes...
 
Public Class BitmapCursor
Implements IDisposable
 
#Region "variables"
 
Private icon_Info As ICONINFO
Private cursor_ As System.Windows.Forms.Cursor = Nothing
Private handle As IntPtr = IntPtr.Zero
 
#End Region
 
#Region "constants"
 
Private Shared ReadOnly TreeviewOffset As System.Drawing.Point = New System.Drawing.Point(-1, 2)
Private Const Alpha As Double = 0.33
 
#End Region
 
_
Private Structure ICONINFO
 
Public fIcon As Boolean
Public xHotspot As System.UInt32
Public yHotspot As System.UInt32
Public hbmMask As IntPtr
Public hbmColor As IntPtr
 
End Structure
 
_
Private Shared Function CreateIconIndirect(ByRef iconinfo As ICONINFO) As IntPtr
End Function
 
_
private Shared Function DestroyIcon(ByVal hIcon As IntPtr) As Boolean
End Function
 
Private Sub Create()
 
handle = CreateIconIndirect(icon_Info)
cursor_ = New Cursor(handle)
 
End Sub
 
Private Sub New(ByVal bmp As System.Drawing.Bitmap, ByVal HotSpotX As Integer, ByVal HotSpotY As Integer)
icon_Info = New ICONINFO()
icon_Info.fIcon = False
icon_Info.xHotspot = 0
icon_Info.yHotspot = 0
icon_Info.hbmMask = bmp.GetHbitmap()
icon_Info.hbmColor = bmp.GetHbitmap()
Create()
 
End Sub
 

' ' Creates a cursor from a bitmap and combines it with another cursor.
'
Private Sub New(ByVal bmp As System.Drawing.Bitmap, ByVal CursorToCombine As System.Windows.Forms.Cursor)
 
icon_Info = New ICONINFO()
icon_Info.fIcon = False
icon_Info.xHotspot = 0
icon_Info.yHotspot = 0
Using bmpdup As System.Drawing.Bitmap = CType(bmp.Clone(), System.Drawing.Bitmap)
Using g As Graphics = System.Drawing.Graphics.FromImage(bmpdup)
CursorToCombine.Draw(g, New System.Drawing.Rectangle(New Point(0, 0), CursorToCombine.Size))
End Using
icon_Info.hbmMask = bmpdup.GetHbitmap()
icon_Info.hbmColor = bmpdup.GetHbitmap()
Create()
End Using
 
End Sub
 
Private ReadOnly Property Cursor() As System.Windows.Forms.Cursor
Get
Return Me.cursor_
End Get
End Property
 
Public Shared Function CreateDragCursor(ByRef NodeToDraw As System.Windows.Forms.TreeNode, ByRef CursorToCombine As System.Windows.Forms.Cursor) As Cursor
 
If Not NodeToDraw Is Nothing Then
Dim NodesBounds As System.Drawing.Rectangle = GetNodesBounds(NodeToDraw)
 
Dim ControlScreenshot As System.Drawing.Bitmap = New System.Drawing.Bitmap(NodeToDraw.TreeView.Width, NodeToDraw.TreeView.Height)
Dim CursorBitmap As System.Drawing.Bitmap = New System.Drawing.Bitmap(Math.Max(NodesBounds.Width, CursorToCombine.Size.Width), Math.Max(NodesBounds.Height, CursorToCombine.Size.Height))
Dim CursorBitmapRectangle As System.Drawing.Rectangle = New System.Drawing.Rectangle(0, 0, NodesBounds.Width, NodesBounds.Height)
 
NodeToDraw.TreeView.DrawToBitmap(ControlScreenshot, New System.Drawing.Rectangle(0, 0, ControlScreenshot.Width, ControlScreenshot.Height))
 
Using g As Graphics = Graphics.FromImage(CursorBitmap)
g.Clear(NodeToDraw.TreeView.BackColor)
g.DrawImage(ControlScreenshot, CursorBitmapRectangle, NodesBounds, GraphicsUnit.Pixel)
End Using
 
Call AdjustAlpha(CursorBitmap, Alpha, NodeToDraw.TreeView.BackColor)
 
Return New BitmapCursor(CursorBitmap, CursorToCombine).Cursor
Else
Return CursorToCombine
End If
 
End Function
 
Private Shared Function GetNodeBounds(ByRef NodeToDraw As System.Windows.Forms.TreeNode) As System.Drawing.Rectangle
 
If Not NodeToDraw Is Nothing Then
Try
Return New Rectangle(NodeToDraw.Bounds.Left - NodeToDraw.TreeView.ImageList.ImageSize.Width + TreeviewOffset.X, NodeToDraw.Bounds.Top + TreeviewOffset.Y, NodeToDraw.Bounds.Width + NodeToDraw.TreeView.ImageList.ImageSize.Width, NodeToDraw.Bounds.Height)
Catch ex As Exception
Return New Rectangle(NodeToDraw.Bounds.Left - 1, NodeToDraw.Bounds.Top + 2, NodeToDraw.Bounds.Width, NodeToDraw.Bounds.Height)
End Try
Else
Return System.Drawing.Rectangle.Empty
End If
 
End Function
 
Private Shared Function GetNodesBounds(ByRef NodeToDraw As System.Windows.Forms.TreeNode) As System.Drawing.Rectangle
 
Dim ChildNode As System.Windows.Forms.TreeNode
Dim Result As System.Drawing.Rectangle
If Not NodeToDraw Is Nothing Then
Result = GetNodeBounds(NodeToDraw)
If NodeToDraw.IsExpanded AndAlso NodeToDraw.Nodes.Count > 0 Then
If NodeToDraw.Nodes.Count > 0 Then
For Each ChildNode In NodeToDraw.Nodes
Result = System.Drawing.Rectangle.Union(Result, GetNodesBounds(ChildNode))
Next ChildNode
End If
End If
Return Result
Else
Return System.Drawing.Rectangle.Empty
End If
 
End Function
^
private Sub AdjustAlpha(ByRef ImageToAdjust As System.Drawing.Bitmap, ByVal Alpha As Double, ByRef TransparencyColor As System.Drawing.Color)
 
Dim ActColor As System.Drawing.Color
Dim x, y As Integer
Dim AlphaByte As Integer = CInt(Math.Round(Alpha * 255))
For x = 0 To ImageToAdjust.Width - 1 Step 1
For y = 0 To ImageToAdjust.Height - 1 Step 1
ActColor = ImageToAdjust.GetPixel(x, y)
If ActColor.R = TransparencyColor.R AndAlso _
ActColor.G = TransparencyColor.G AndAlso _
ActColor.B = TransparencyColor.B Then
ImageToAdjust.SetPixel(x, y, Color.FromArgb(0, ActColor))
Else
ImageToAdjust.SetPixel(x, y, Color.FromArgb(AlphaByte, ActColor))
End If
Next y
Next x
 
End Sub
 
Protected Overrides Sub Finalize()
 
Dispose(False)
 
End Sub
 
Private Sub Dispose() Implements IDisposable.Dispose
 
GC.SuppressFinalize(Me)
Dispose(True)
 
End Sub
 
Private Sub Dispose(ByVal disposing As Boolean)
 
Try
If Not handle = IntPtr.Zero Then
DestroyIcon(handle)
End If
Catch ex As Exception
 
End Try
 
End Sub
 
End Class
Generalbug found and fixed it PinmemberIng Bram Boucherie5:32 15 Apr '08  
General[fix]Custom Nodes and moving PinmemberKbirger5:01 24 Aug '05  
GeneralOnDragLeave PinmemberPoele2:41 3 Jun '05  
GeneralRe: OnDragLeave PinmemberKbirger4:49 24 Aug '05  
Questionmy node doesnt work ???? Pinmembergyokusei17:25 29 Apr '05  
GeneralSmall fix to your code Pinmemberrasw18:46 27 Jan '05  
QuestionCan I do it from VB.net PinsussMichel Aparecido5:28 3 Jan '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.

Permalink | Advertise | Privacy | Mobile
Web03 | 2.5.120529.1 | Last Updated 1 Aug 2004
Article Copyright 2004 by yetanotherchris
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid