Two ListBoxes - Drag and Drop Example






4.26/5 (18 votes)
Jan 19, 2004
8 min read

233531

7264
Example of a windows form application with two listboxes. Items from the left listbox can be dragged to the right listbox. Items in the right listbox can be rearranged or removed with DragDrop.
Introduction
This presents a windows form application with two listboxes. Items from the
left listbox (listBox1) can be dragged to the right listbox (listBox2). Items in
the right listBox2 can be rearranged or removed. Drag and Drop events are
trapped and reported in a third listbox and informational label. DragDrop
events are heavily instrumented and reported.
Background
This article attempts to convey my understanding of the mechanics, events, and hidden objects that are involved with drag and drop operations after a couple days of experimentation. This article hopefully explains where the "selected" data item is stored temporarily at the start of drag and how the data item is retrieved at "drop" time.
DragAndDrop operations depend heavily on asynchronous events. I found that following these events with break points in the VS debugger difficult. Therefore, from within each event, I write out various values to the third or bottom listbox3 and also to a informational label1. For clarity, the code which produces the informational displays (lines involving listBox3 and label1) is not displayed in the code sections discussion which follow.
Using the code
The intended application for this example required allowing the user choose a desired set of listView columns from a fixed set (represented by the left listBox listBox1). The user chooses which columns he wants desplayed by dragging from the left to the right listBox (listBox2). Additionally the application needed to allow the user to move or rearrange the columns in the right listBox and to remove one or more at will, all with drag and drop operations.
When the user left clicks on an item in the left listBox1, a mouse down event
is fired. The column name (like Latitude etc) index in the left listBox
(actually a string
) is computed using the listBox method "Index
from Cursor Position". If the index is valid (ie, the index is one of the
listBox string items) the magic DoDragDrop
method is called.
Essentially that event looks like this
MouseDown
event starts a DoDragDrop
operation
private void listBox1_MouseDown(object sender,
System.Windows.Forms.MouseEventArgs e)
{
int indexOfItem = listBox1.IndexFromPoint(e.X, e.Y);
if(indexOfItem >= 0 && indexOfItem
< listBox1.Items.Count) // check we clicked down on a string
{
// Set allowed DragDropEffect to Copy selected
// from DragDropEffects enumberation of None, Move, All etc.
listBox1.DoDragDrop(listBox1.Items[indexOfItem], DragDropEffects.Copy );
}
}
All controls (textBox, listBox button, etc all have a DoDragDrop
method which can be invoked in a similar manner
(control.DoDragDrop
). That method arrives at each control (textBox,
listBox, button, etc) because they are all derived (inherited) from the more
primary base "control".
If you look up Control.DoDragDrop
method, you will see that the
first required parameter is an object of type IDataObject
( which
is in this example a string object snatched from an array of strings that
constitute the guts of the left listBox's Items collection). The second
parameter is an allowedEffect DragDropEffect
taken from the
selection enumerated listing of all DragDropEffects
which are
"All
", "Copy
", "Link
",
"Move
", "None
", and "Scroll
". Here I want
to permit strings from the left listBox1 to be copied via drag and drop.
Therefore parameter two, is set to "DragDropEffects.Copy
"
All well and good so far. Execution on the MouseDown
block is
completed, the listBox1_MouseDown
has done it's work. Execution
waits for the next event. But what happened to my string that I want to move?
Here's the hidden magic worthy of the Grand Master Anders H. Somewhere in the
bowels of your PC, DoDragDrop
has created an object for you. It is
an object of type DataObject
but an object none the less just like
your left listBox1 is an object and the items in it are an array of objects of
type String. In this DataObject
is buried your selected listBox1
string item (a String
type object). Click on "Bearing" from the
left listBox and your string will be "Bearing".
But how do I get this string back from the DataObject
when I'm
ready to deposit it in the right hand listBox? More magic. Once you have set in
motion the DoDragDrop
operation, the pump is primed so to speak.
Put the cursor on one of the items in the left listBox, depress the left mouse
button and look at the cursor. It changes to a "dragdrop" copy symbol. If the
drag is continued on into listBox2 (whose listBox2.AllowDrop
property is set = true
), a listBox2.DragEnter
event is fired. In this event's arguments, you are passed access to the
object created by the DoDragDrop
call initiated in the
listBox1_MouseDown event. Buried in the event argument "e" is your
string
. Its an object of type
System.Windows.Forms.DragEventArgs
and is passed to you by the
DoDragDrop
method. Here's the essential lines of the event
code:
DragEnter
Event triggered when dragging Cursor enters right
listBox
private void listBox2_DragEnter(object sender,
System.Windows.Forms.DragEventArgs e)
{
// change the drag cursor to show valid data ready
if (e.Data.GetDataPresent(DataFormats.StringFormat) &&
(e.AllowedEffect == DragDropEffects.Copy|DragDropEffects.Move) )
e.Effect = DragDropEffects.Copy;
else
e.Effect = DragDropEffects.Move;
}
How do you get your string
? Call the DragEventArg
object
e
's method(s)
e.Data.GetData
. Buried in there is your string
which
you see displayed in the informational listBox3
item just added!
Here we signal to the user that a copy operation is valid by keeping the cursor
shape reflect that the DragDrop
operation has entered an area where
a copy is appropriate (remember Cntrl + C in the clipboard operations?... C for
Copy, same idea). For drill, put a break point in the above block and then a
watch on "e". Keep drilling down clicking on the + signs. Eventually you'll
uncover your selected string . The demo code further queries the wondrous
DragEventArgs
object to display just some of the information it
contains then reports this out to the bottom listBox3
.
Here too we check to see that the DataObject's
is reasonable and
not a Locale, a Pallette, a Bitmap or one of the other formats that you can
legally drag and drop. (See a complete list in the Visual Studio .NET Doc by
searching for "DataFormat
class").
So we can get at our string
(it's a valid
typeDataFormat.StringFormat
and retrievable from calling
e.Data.GetData
), how do we insert it into the right hand listBox?
Read on...
After the cursor is changed in the last few lines of the
listBox2_DragEnter
event block (above), execution on code DragEnter
block above terminates. Then the DoDragOperation
fires the
listBox2_DragOver
event (below) many times. This firing will
rapidly fill your informational list box (listBox3). Here's the DragOver
event looks like:
DragOver
event repeatedly Triggered. Insertion drop index in
listBox2
determined.
private void listBox2_DragOver(object sender,
System.Windows.Forms.DragEventArgs e)
{
indexOfItemUnderMouseToDrop =
listBox2.IndexFromPoint(listBox2.PointToClient(
new Point(e.X, e.Y)));
if (indexOfItemUnderMouseToDrop != ListBox.NoMatches)
{
label1.Text = "\'"+e.Data.GetData(DataFormats.Text)+"\'"+
" will be placed before item #" + (indexOfItemUnderMouseToDrop + 1)+
"\n which is "+ listBox2.SelectedItem;
listBox2.SelectedIndex = indexOfItemUnderMouseToDrop;
}
else
{
label1.Text = "\'"+e.Data.GetData(DataFormats.Text)+ "\'"+
" will be added to the bottom of the listBox." ;
listBox2.SelectedIndex = indexOfItemUnderMouseToDrop;
}
if(e.Effect == DragDropEffects.Move)
listBox2.Items.Remove((string)e.Data.GetData(DataFormats.Text));
// fill the info listBox
}
As I said, this event is executed repeatedly and rapidly. Here you determine
where the string passed in the data object will be placed. You determine the
intended insertion index by first translating the DragOver
event's
mouse screen coordinates e.X
and e.Y
to client
coordinates of listBox2. The translation is accomplished by a call to
listBox2.PointToClient( )
Then the call
listBox2.IndexFromPoint
can do it's work and tell us over which
item the cursor is positioned over. We save that index in
indexOfItemUnderMouseToDrop
which is used in the next event.
Finally, when you release the left mouse button , the DoDragDrop
operation causes execution of the listBox2_DragDrop
event
which is shown below. Here you make sure the index is valid ( index
>=0
but < total
strings). If the insertion index is
valid, we call e.Data.GetData()
and insert the the retrived
string
. If the index was not valid, the retrieved string
is simply added to the collection of listBox2.Items
.
DragDrop
triggered at MouseButton
Up
-
String inserted into listBox2
private void listBox2_DragDrop(object sender,
System.Windows.Forms.DragEventArgs e)
{
if(e.Data.GetDataPresent(DataFormats.StringFormat))
// dropped data still a string?
{
if(indexOfItemUnderMouseToDrop >= 0 &&
indexOfItemUnderMouseToDrop < listBox2.Items.Count)
{
listBox2.Items.Insert(indexOfItemUnderMouseToDrop,
e.Data.GetData(DataFormats.Text));
}
else
{
// add the selected string to bottom of list
listBox2.Items.Add(e.Data.GetData(DataFormats.Text));
}
}
}
listBox QuertyContinuingDrag
event cancels DragDrop
if cursor wonders outside Form1
Both listBox1 and listBox2 have delegate events that do nothing more than
cancel the DoDragDrop operation, if while holding your left button down and
dragging, you wander outside the boundaries of Form1. (The code I used was
snipped from M$ .NET Doc for Control.DoDrapDrop
method.) The
documentation says this event is triggered after a DoDragDrop
operation is started, "if there is a change in the keyboard or mouse
button state". It seems to me that mouse movement also triggers this event. The
event code is the same forlistBox1_QueryContinuingDrag
event as it
is for listBox2_QueryContinueDrag
even. Here's the start of
listBox1_QueryContineDrag
:
private void listBox1_QueryContinueDrag(object sender,
System.Windows.Forms.QueryContinueDragEventArgs e)
The events arguments allow access to certain DragDrop
operational properties. e.Action
can be set to
DragAction.Cancel
to stop the DrapDrop
operation
without fanfare which is the desired operation if you wander outside
Form1
. If you don't cancel outside Form1, you can drop your
selected string anywhere including VisualStudio's editor. (unintended and
certainly annoying). Once this superfluous string is dumped, the next build will
complain about not knowing what Bearing or Distance,etc is for.
Re-ordering or rearranging Items in the right listBox
Recall that my application needed a way to allow the right hand listBox2's entries to be removed or rearranged. This is how that feature was accomplished.
If the left mouse button is clicked down and held in the right hand listBox2,
the corresponding listBox2_MouseDown
event is triggered. The item
under the mouse is determined by getting its index and the DoDragDrop
operation is triggered. The DragEnter
and DragOver
events are triggered in the same order as before. The index of the new
insertion point is determined and inserted upon release of the left mouse
button.
How the System knows a DragDrop
from a ordinary mouse
movements.
A DragDrop
operation is not triggered until the left mouse
button is held down and the mouse cursor is moved a system set number of pixels
. This number is determined in SystemInformation.DragSize
. On my
PC, my left button has to be held down and the cursor moved four pixels left or
right or up or down to initiate a DragDrop
operation as reflected
by the cursor change. (On my SystemInformation.DragSize == 4
). This
prevents a simple left click or multiple clicks within a control from starting
DragDrop.
History
- Version 1 released: January 2004.