The code below assumes you are working in a new class named
ListBox that inherits from System.Windows.Forms.ListBox.
As it's only implementation details that are different between
the two versions, I'll include the pertinent code and point out
the differences but that's about it. As always a full example
project is available from the link at the end of the article.
As with the previous article, you must set AllowDrop to
true on any ListBox you wish to make use of this
functionality.
Drawing on a ListBox
Just like the ListView, the ListBox control is a native
control that is drawn by the operating system and so overriding
OnPaint doesn't work. The ListBox also has a unique
behaviour of built in owner draw support, so you have to make
sure your painting works with all modes.
Fortunately, the exact same method of painting I used with the
ListView works fine here too - that is, I capture WM_PAINT
messages and use Graphics.FromControl to get something I can
work with.
The only real difference is getting the boundaries of the item
to draw due to the differences in the API's of the two controls
- the ListView uses ListViewItem.GetBounds whilst the
ListBox version is ListView.GetItemRectangle.
Flicker flicker flicker
The ListBox is a flickery old beast when owner draw is being
used. Unlike the ListView control where I just invalidate the
entire control and trust the double buffering, unfortunately
setting double buffering on the ListBox seems to have no
effect and it flickers like crazy as you drag things around.
To help combat this, I've added a custom Invalidate method
that accepts the index of a single item to redraw. It also
checks if an insertion mode is set, and if so adjusts the bounds
of the rectangle to include the next/previous item (otherwise,
bits of the insertion guides will be left behind as it tries to
flicker free paint). It will then invalidate only that specific
rectangle and reduce overall flickering. It's not perfect but
it's a lot better than invalidating the whole control.
When you call Control.Invalidate it does not trigger an
immediate repaint. Instead it sends a WM_PAINT message to
the control to do a paint when next possible. This means
multiple calls to Invalidate with custom rectangles will
more than likely have them all combined into a single large
rectangle, thus repainting more of the control that you might
anticipate.
As it would be somewhat confusing to the user (not to mention
rude) if we suddenly initiated drag events whenever they click
the control and their mouse wiggles during it, we check to see
if the mouse cursor has moved sufficient pixels away from the
drag origin using metrics obtained from SystemInformation.
If the user has dragged the mouse outside this region, then we
call DoDragDrop to initialize the drag and drop operation.
Updating the insertion index
In exactly the same way as with the ListView version, 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 logic is the same, just the implementation differences in
getting the hovered item (use ListBox.IndexFromPoint and the
item bounds). I've also added a dedicated InsertionMode.None
option this time, which is mainly so I don't unnecessarily
invalidate larger regions that I wanted as described in "Flicker
flicker flicker" above.
If the mouse leaves the confines of the control, then we use the
DragLeave event to reset the insertion status. Again no
differences per se, I set the insertion mode now, and I also
call Invalidate first with the current index before resetting
it.
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.
Just as simple as the ListView version!
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?
Hi Richard,
First of all thank You for great article, once again it helped me a lot.
Right now I'm trying to create something similar for Datagridview control, but I have no idea where should I start.
You've created two extended components: ListView and ListBox, maybe DataGridView won't be a problem to You.
Maybe You could point me where I can start with this?
Many thanks for all Your great work
You can't, at least automatically - this is not functionality present in a ListBox control (which hearkens all the way back to Windows 3.1 at least). For this sort of functionality, you'd have to roll your own inline editing functionality. Alternatively, look at a ListView control, this supports built in automatic editing although I think that is only for the first column.
As another alternative, look into something like the DataGrid which fully supports assorted editors for each cell.
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.
In my last post, I described how to [drag and drop items to reorder a `ListView` control](/blog/dragging-items-in-a-listview-control-with-visual-insertion-guides). This time I'm going to describe the exact same technique, but this time for the more humble `ListBox`.
# Misiu
# Schorge
# Richard Moss