Click here to Skip to main content
Click here to Skip to main content
Go to top

Two ListBoxes - Drag and Drop Example

, 18 Jan 2004
Rate this:
Please Sign up or sign in to vote.
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 MouseButtonUp - 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.

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

Share

About the Author

Larry1024
Web Developer
United States United States
Retired electronics engineer who programs as a hobby.

Comments and Discussions

 
GeneralMy changes [modified] PinmemberThymine27-Jan-10 10:57 
GeneralIs this a Bug PinmemberMember 20432344-Jun-08 19:39 
GeneralRe: Is this a Bug Pinmembersubwaybmt11-Dec-09 3:14 
GeneralTest application Failed Pinmembersreejith ss nair9-Oct-07 0:27 
GeneralSwap instead of add/remove PinmemberzPilott28-Mar-06 7:55 
GeneralIt doesn't work PinsussAnonymous5-Jun-05 19:23 
GeneralRe: It doesn't work PinsussAnonymous5-Jun-05 19:26 
GeneralDragDrop Example Not Quite Working PinmemberKousharPGCom28-Jan-04 7:51 
GeneralRe: DragDrop Example Not Quite Working PinmemberLarry102428-Jan-04 11:06 
GeneralAnother sample PinmemberMarkri19-Jan-04 8:49 
GeneralRe: Another sample PinmemberLarry102419-Jan-04 9:57 
GeneralRe: Another sample PinmemberMadhusudanan9-Jul-04 0:54 

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.

| Advertise | Privacy | Mobile
Web01 | 2.8.140922.1 | Last Updated 19 Jan 2004
Article Copyright 2004 by Larry1024
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid