I can't remember when it was I first saw something being dragged
with an insertion mark for guidance. Whenever it was, it was a
long long time ago and I'm just catching up now.
This article describes how to extend a ListView control to
allow the items within it to be reordered, using insertion
guides.
Drag Drop vs Mouse Events
When I first decided that one of my applications needed the
ability to move items around in a list, I knocked together some
quick code by using the MouseDown, MouseMove and MouseUp
events. It worked nicely but I ended up not using it, for the
simple reason I couldn't work out how to change the cursor one
of the standard drag/drop icons - such as Move, Scroll, or
Link. So if anyone knows how to do this, I'd be happy to hear
how (note I don't mean assigning a custom cursor, I mean using
the true OS cursor). As it turns out, apart from the cursor
business (and the incidental fact it looks... awful... if you
enable shell styles), it's also reinventing a fairly large
wheel as the ListView control already provides most of what we
need.
An Elephant Never Forgets: The AllowDrop Property
I should probably have this tattooed upon my forehead as I
think that whenever I try and add drag and drop to anything
and it doesn't work, it's invariably because I didn't set the
AllowDrop property of the respective object to true. And
invariably it takes forever until I remember that that has to
be done.
So... don't forget to set it!
Getting Started
The code below assumes you are working in a new class named
ListView that inherits from System.Windows.Forms.ListView.
You could do most of this by hooking into the events of an
existing control, but that way leads to madness (and more
complicated code). Or at least duplicate code, and more than
likely duplicate bugs.
Drawing insertion marks
I originally started writing this article with the drag sections
at the start, followed by the sections on drawing.
Unfortunately, it was somewhat confusing to read as the drag is
so heavily dependant on the drawing and insertion bits. So I'll
talk about that first instead.
In order to draw our guides, and to know what to do when the
drag is completed, we need to store some extra information - the
index of the item where the item is to be inserted, and whether
the item is to be inserted before or after the insertion item.
Leaving behind the question on if that sentence even makes
sense, on with some code!
As we'll be drawing a nice guide so the user is clear on what is
happening, we'll also provide the property to configure the
colour of said guide.
We'll also need to initialize default values for these.
Notice the call to set the DoubleBuffered property? This has
to be done, otherwise your drag operation will be an epic
exercise of Major Flickering. In my library code I use the
LVS_EX_DOUBLEBUFFER style when creating the window, but in
this example DoubleBuffered has worked just as well and is
much easier to do.
Drawing on a ListView
The ListView control is a native control that is drawn by the
operating system. In otherwords, overriding OnPaint isn't
working to work.
So how do you draw on it? Well, you could always go even more
old school than using Windows Forms in the first place, and use
the Win32 to do some custom painting. However, it's a touch
overkill and we can get around it for the most part.
Instead, we'll hook into WndProc, watch for the WM_PAINT
message and then use the Control.CreateGraphics method to get
a Graphics object bound to the window and do our painting that
way.
I'm not really sure that using CreateGraphics is the best way
to approach this, but it seems to work and it was quicker than
trying to recall all the Win32 GDI work I've done in the past.
Tip: The DebuggerStepThrough is useful for stopping the
debugger from stepping into a method (including any manual
breakpoints you have created). WndProc can be called
thousands of times in a "busy" control, and if you're trying
to debug and suddenly end up in here, it can be a pain.
The code for actually drawing the insertion line is in itself
simple enough - we just draw a horizontal line with arrow heads
at either side. We adjust the start and end of the line to
ensure it always fits within the client area of the control,
regardless of if the control is horizontally scrolled or the
total width of the item.
And that's all there is to that part of the code. Don't forget
to ensure the control is double buffered!
Initiating a drag operation
The ListView control has an ItemDrag event that is
automatically raised when the user tries to drag an item. We'll
use this to initiate our own drag and drop operation.
Note: The code snippets in this article are kept concise to
show only the basics of the technique. In most cases, I've
expanded upon this to include extra support (in this case for
raising an event allowing the operation to be cancelled),
please download the sample project for the full class.
When the DroDragDrop is called, execution will halt at that
point until the drag is complete or cancelled. You can use the
DragEnter, DragOver, DragLeave, DragDrop and
GiveFeedback events to control the drag, for example to
specify the action that is currently occurring, and to handle
what happens when the user releases the mouse cursor.
Updating the insertion index
We can use the DragOver event to determine which item the
mouse is hovered over, and from there calculate if this is a
"before" or "after" action.
The code is a little long, but simple enough. We get the
ListViewItem underneath the cursor. If there isn't one, we
clear any existing insertion data. If we do have one, we check
if the cursor is above or below half of the total height of the
item in order to decide "before" or "after" status.
We also inform the underlying drag operation so that the
appropriate cursor "Move" or "No Drag" is displayed.
Finally, we issue a call to Invalidate to force the control to
repaint so that the new indicator is drawn (or the existing
indicator cleared).
If the mouse leaves the confines of the control, then we use the
DragLeave event to reset the insertion status. We don't need
to use DragEnter as DragOver covers us in this case.
Handling the drop
When the user releases the mouse, the DragDrop event is
raised. Here, we'll do the actual removal and re-insertion of
the source item.
We reuse the InsertionIndex and InsertionMode values we
calculated in OnDragOver and then determine the index of the
new item from these. Remove the source item, reinsert it at the
new index, then clear the insertion values, force and repaint
and we're done. Easy!
Sample Project
An example demonstration project with an extended version of the
above code is available for download from the link below.
Update History
2014-07-27 - First published
2020-11-21 - Updated formatting
Like what you're reading? Perhaps you like to buy us a coffee?
Thanks for your comment. Unfortunately, I don't believe this will be entirely possible in VBA as this is based on Visual Basic 6, and I don't think the VB6 list view exposed all the necessary functionality. For example, .NET provides a WndProc override, making it trivial to intercept Windows messages. VB on the other hand, walled this away and made it very difficult - I remember countless times I made VB6 "disappear" by faulting when hooking messages when I used to work with VB.
So while I can't say for certain (it's been many a year since I touched VB, and even longer since I touched VBA (and then only for simple macros)), I don't think it's possible.
Thanks for the nice job. And I found a bug,I dragged 1 after 2, and then I drag 1 just let the clorline before or after itself, but it will move 1 to 3 if the colorline is on the bottom of the item 1.How to fix this bug? thank you
// Scroll top or bottom for dragging item
if (this.InsertionIndex > -1 && this.InsertionIndex < this.Items.Count)
{
EnsureVisible(this.InsertionIndex);
}
base.OnDragOver(drgevent);
The founder of Cyotek, Richard enjoys creating new blog content for the site. Much more though, he likes to develop programs, and can often found writing reams of code. A long term gamer, he has aspirations in one day creating an epic video game - but until that time comes, he is mostly content with adding new bugs to WebCopy and the other Cyotek products.
I can't remember when it was I first saw something being dragged with an insertion mark for guidance. Whenever it was, it was a long long time ago and I'm just catching up now.
This article describes how to extend a `ListView` control to allow the items within it to be reordered, using insertion guides.
# Miguel F. Augusto
# Richard Moss
# pc8181
# Austin
# John
# Henrik
# Motaz Alnuweiri