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.