I'm working on a file organizer of sorts. The user can drag icons from the desktop into a list view, and drag items out of the list view to remove them.

Dragging icons from the desktop to the list was straightforward, it's the other part that's giving me trouble. What I want to happen is:

  1. User drags item
  2. User releases mouse button
  3. Application detects whether or not item was dragged outside of listview
  4. If it was dragged outside of the listview, remove it from the list

The problem occurs on step two. If the user drags an item outside of the form then the MouseUp event won't fire until the cursor is back over the form. It feels very unintuitive to move the mouse before your icon vanishes, and I'd really prefer the icon to disappear instantly.

I've been messing around with a bunch of Window API calls (WndProc, SetCapture, etc.), to try and detect when the user releases the mouse button, but I haven't had any luck yet. I can't help but think there must be a simpler way to do this.

The ListView supports the "DragLeave" event. Would this help? Once the event fires, you can check to see what's being dragged and if its an item from the ListView, you can remove it.

I tried something like that, only with the MouseLeave event. The problem with this technique is that the item gets removed as soon as it hits the edge of the ListView. I want the removal to stayed tied with them releasing the left mouse button. That way they have a chance to "cancel" the removal by dragging the item back over the list before triggering the MouseUp event.

Found a solution! (or at least a workaround)

Instead of using MouseUp or an API call (neither of which I now believe will logically work) I set up a Timer object. When the user clicks down on an object in the list the timer starts. Every 10th of a second it checks to see if the left mouse button has been released. If it has, and the mouse is outside the form, the item gets removed from the list.

The relevant code:

using System.Timers;

public class form1: Form
   {
   
   private System.Timers.Timer clickDetector;

   private ListView listToRemoveFrom;
   private ListViewItem itemBeingMoved;

   public form1()
      {
         InitializeComponent();

         //We create the timer, set it to go off every tenth of a second,
         //and set the method to be done at each interval.
         clickDetector = new System.Timers.Timer();
         clickDetector.Interval = 100;
         clickDetector.Elapsed += checkMouseState;
      }

   //This function checks whether the mouse button is no longer up (i.e. the mouseButton
   //is property is "None") and if the mouse is outside the form. If so remove the item
   //being dragged
   public void checkMouseState(object source, ElapsedEventArgs e)
      {
         if(outsideOfForm() && Control.MouseButtons.ToString() == "None")
            removeItemFromList();
      }

   //Is the mouse outside of the form?
   public bool outsideOfForm()
      {
         if (MousePosition.X < this.Location.X || MousePosition.X > (this.Location.X + this.Width) ||
             MousePosition.Y < this.Location.Y || MousePosition.Y > (this.Location.Y + this.Height))
         {
            return true;
         }

         return false;
      }

      public delegate void setRemoveItemFromList();

      public void removeItemFromList()
         {
            /* The timer runs on a separate thread than the form, so we have to use
               a bit of "delegate magic" to access a control on the form */
            if (listToRemoveFrom.InvokeRequired)
               {
               setRemoveItemFromList remove = new setRemoveItemFromList(removeItemFromList);
               this.Invoke(remove);
            }
            else
            {
                listToRemoveFrom.Items.Remove(itemBeingMoved);
                
                //There's no point in having the timer run all the time
                clickDetector.Stop();
            }
        }

     private void listView1_MouseDown(object sender, MouseEventArgs e)
        {
            //Start the timer only if the left mouse button has clicked on an item
            //in the listView
            if (e.Button != MouseButtons.Left)
            {
                return;
            }

            if (((ListView)sender).GetItemAt(e.X, e.Y) == null)
            {
                return;
            }

            clickDetector.Start();

            listToRemoveFrom = (ListView)sender;
            itemBeingMoved = ((ListView)sender).GetItemAt(e.X, e.Y);
        }
   }
Comments
Innovative solution! Thanks for sharing!
This article has been dead for over six months. Start a new discussion instead.