views:

2718

answers:

4

Is there an easy way to ensure that after a drag-and-drop fails to complete, the MouseUp event isn't eaten up and ignored by the framework?

I have found a blog post describing one mechanism, but it involves a good deal of manual bookkeeping, including status flags, MouseMove events, manual "mouse leave" checking, etc. all of which I would rather not have to implement if it can be avoided.

+8  A: 

I was recently wanting to put Drag and Drop functionality in my project and I hadn't come across this issue, but I was intrigued and really wanted to see if I could come up with a better method than the one described in the page you linked to. I hope I clearly understood everything you wanted to do and overall I think I succeeded in solving the problem in a much more elegant and simple fashion.

On a quick side note, for problems like this it would be great if you provide some code so we can see exactly what it is you are trying to do. I say this only because I assumed a few things about your code in my solution...so hopefully it's pretty close.

Here's the code, which I will explain below:

this.LabelDrag.QueryContinueDrag += new System.Windows.Forms.QueryContinueDragEventHandler(this.LabelDrag_QueryContinueDrag);
this.LabelDrag.MouseDown += new System.Windows.Forms.MouseEventHandler(this.LabelDrag_MouseDown);
this.LabelDrag.MouseUp += new System.Windows.Forms.MouseEventHandler(this.LabelDrag_MouseUp);

this.LabelDrop.DragDrop += new System.Windows.Forms.DragEventHandler(this.LabelDrop_DragDrop);
this.LabelDrop.DragEnter += new System.Windows.Forms.DragEventHandler(this.LabelMain_DragEnter);

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void LabelDrop_DragDrop(object sender, DragEventArgs e)
    {
        LabelDrop.Text = e.Data.GetData(DataFormats.Text).ToString();
    }


    private void LabelMain_DragEnter(object sender, DragEventArgs e)
    {
        if (e.Data.GetDataPresent(DataFormats.Text))
            e.Effect = DragDropEffects.Copy;
        else
            e.Effect = DragDropEffects.None;

    }

    private void LabelDrag_MouseDown(object sender, MouseEventArgs e)
    {
        //EXTREMELY IMPORTANT - MUST CALL LabelDrag's DoDragDrop method!!
        //Calling the Form's DoDragDrop WILL NOT allow QueryContinueDrag to fire!
        ((Label)sender).DoDragDrop(TextMain.Text, DragDropEffects.Copy); 
    }

    private void LabelDrag_MouseUp(object sender, MouseEventArgs e)
    {
        LabelDrop.Text = "LabelDrag_MouseUp";
    }

    private void LabelDrag_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
    {
        //Get rect of LabelDrop
        Rectangle rect = new Rectangle(LabelDrop.Location, new Size(LabelDrop.Width, LabelDrop.Height));

        //If the left mouse button is up and the mouse is currently over LabelDrop
        if (Control.MouseButtons != MouseButtons.Left && !rect.Contains(PointToClient(Control.MousePosition)))
        {
            //Cancel the DragDrop Action
            e.Action = DragAction.Cancel;
            //Manually fire the MouseUp event
            LabelDrag_MouseUp(sender, new MouseEventArgs(Control.MouseButtons, 0, Control.MousePosition.X, Control.MousePosition.Y, 0));
        }
    }

}

I have left out most of the designer code, but included the Event Handler link up code so you can be sure what is linked to what. In my example, the drag/drop is occuring between the labels LabelDrag and LabelDrop.

The main piece of my solution is using the QueryContinueDrag event. This event fires when the keyboard or mouse state changes after DoDragDrop has been called on that control. You may already be doing this, but it is very important that you call the DoDragDrop method of the control that is your source and not the method associated with the form. Otherwise QueryContinueDrag will NOT fire!

One thing to note is that QueryContinueDrag will actually fire when you release the mouse on the drop control so we need to make sure we allow for that. This is handled by checking that the Mouse position (retrieved with the global Control.MousePosition property) is inside of the LabelDrop control rectangle. You must also be sure to convert MousePosition to a point relative to the Client Window with PointToClient as Control.MousePosition returns a screen relative position.

So by checking that the mouse is not over the drop control and that the mouse button is now up we have effectively captured a MouseUp event for the LabelDrag control! :) Now, you could just do whatever processing you want to do here, but if you already have code you are using in the MouseUp event handler, this is not efficient. So just call your MouseUp event from here, passing it the necessary parameters and the MouseUp handler won't ever know the difference.

Just a note though, as I call DoDragDrop from within the MouseDown event handler in my example, this code should never actually get a direct MouseUp event to fire. I just put that code in there to show that it is possible to do it.

Hope that helps!

Adam Haile
learned a lot from your solution!thanks!
Pablo Santa Cruz
A: 

@Dana

If we knew more about your intentions perhaps we could recommend a solution that isn't as complicated as Adam's.

I'm not trying to say that you are wrong here, but I'm just curious why you think it's complicated? Yes, what I showed you was a lot of code, but I was just trying to give a complete exapmple. The only thing I added beyond what you would already absolutely need to do Drag and Drop is the QueryContinueDrag event, which is 5 lines of code (minus comments).

And I agree, that the OnDrag event could be considered a special case of MouseUp, but there are just some times when you really need that event that is getting swallowed by the system, for whatever your reasons may be. For example, in Compact Framework, the TextBox control does not expose the Mouse events (for touch screen click), but I actually needed it...in the end I had to create my own control to do it.

Though, as I said...code would have been helpful to know what exactly was needed.

Anyway...just my $.02

Adam Haile
A: 

Adam Haile said:

On a quick side note, for problems like this it would be great if you provide some code so we can see exactly what it is you are trying to do.

I thought about doing that, but the context was so complicated, and I was in a hurry when I posted the question. I'll concede that is not the ideal way to post any question to any forum... But I did try to formulate the question in a very specific way as to indicate the effect I wanted: I need something to occur when a drag-and-drop completes unsuccessfully, i.e. drops to an invalid target. At the time of my post, the most obvious way for this to occur, to my mind, was to let through the MouseUp event.

Adam Haile said:

Just a note though, as I call DoDragDrop from within the MouseDown event handler in my example, this code should never actually get a direct MouseUp event to fire. I just put that code in there to show that it is possible to do it.

Understood. If the MouseUp event were not going to be directly fired (or if I wanted the same effect from a direct one as from an indirect one), I would put the desired code in a normal function and then call that from that point (and from within the direct event, if needed there too), instead of "faking" the event.

At any rate, thanks for your code, it really clears things up. I started to implement what I thought I needed before I really understood the drag-and-drop event model, and by the time I figured out that I wasn't going to get a MouseUp event like I wanted, the whole mechanism was already more complicated than it needed to be. Which prevented me from getting to the simple solution you offered.

Chris Ammerman
A: 

Adam, you are the man! That works awesomely.