13

I have a Drag() method on form_MouseDown event. I also have a click event on the form. The problem is that if I click on the form, MouseDown event gets triggered and it never gets the chance to trigger click event.

What is the best way to solve this issue? I was thinking counting pixels if the form is actually dragged, but there has to be a better way. Any suggestions?

Abdusalam Ben Haj
  • 5,343
  • 5
  • 31
  • 45
Kristian
  • 1,348
  • 4
  • 16
  • 39
  • "counting pixels" is quite common, because in the most cases you actually **don't want** the DRAG to fire off when the 'movement distance' is shorter than, for example, 7..15 pixels. Even windows's desktop has such few-pixel threshold. – quetzalcoatl Mar 10 '13 at 12:48
  • 2
    Do not use `Click`, and use `MouseUp` instead? – Victor Zakharov Mar 10 '13 at 12:50
  • I created a demo and I'm not getting this behavior. After MouseDown Click is reached. Can you post some code? – E.T. Mar 10 '13 at 12:53
  • 1
    Maybe he also 'acks' the event with Handled=true or forgets to call the base implementation, etc? – quetzalcoatl Mar 10 '13 at 12:57
  • @Neolisk How will that solve the problem? The `MouseUp` event still gets raised when the user releases the mouse button after a drag. The `Click` event is actually just the event raised after a cycle of `MouseDown` and `MouseUp` events. – Cody Gray - on strike Mar 10 '13 at 13:01
  • @CodyGray: common handler for Drag and the actual click. Two conditional branches for either action inside that. Click would have its own branch otherwise, and would require additional code to sync with MouseDown and MouseUp. In any case, it's hard to say without more details from the OP. – Victor Zakharov Mar 10 '13 at 13:04

3 Answers3

5

I was thinking counting pixels if the form is actually dragged, but there has to be a better way.

Nope, that's exactly how you have to do it.

This isn't just a software limitation; it's very much a practical one as well. If you think through the problem from a user's perspective, you'll immediately see the problem as well as the solution. Ask yourself, what is the difference between a click and a drag?

Both of them start with the mouse button going down over the object, but one of them ends with the mouse button going back up over the object in the same position and the other one ends with the mouse button going back up in a completely different position.

Since time machines haven't been perfected yet, you have no way of knowing this in advance.

So yes, you need to maintain some kind of a distance threshold, and if the pointer moves outside of that distance threshold while it is down over the object, then you consider it a drag. Otherwise, you consider it a click.

That distance threshold should not be 0. The user should not be required to hold the mouse completely still in order to initiate a click. A lot of users are sub-par mousers. They are very likely to twitch slightly when trying to click. If the threshold is 0, they'll end up doing a lot of inadvertent dragging when they try to click.

Of course, you don't actually have to worry about any of this or compute the drag threshold yourself. Instead, use the Windows default values, obtainable by calling the GetSystemMetrics function and specifying either SM_CXDRAG or SM_CYDRAG. (These might be exposed somewhere by the WinForms framework, but I don't think so. It's just as easy to P/Invoke them yourself.)

const int SM_CXDRAG = 68;
const int SM_CYDRAG = 69;
[DllImport("user32.dll")]
static extern int GetSystemMetrics(int index);

Point GetDragThreshold()
{
    return new Point(GetSystemMetrics(SM_CXDRAG), GetSystemMetrics(SM_CYDRAG));
}

In the field of UX/UI, this sort of thing is called hysteresis or debouncing, by analogy to the use of these terms in physics and electronics.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • While you have good points overall here, the program should be designed in a way that drag-and-dropping anything 5px to the side does not cause anything to happen. Otherwise you never know, maybe that 5px shake is not enough for some people to perform a click, and they need 10px. With proper implementation, mouse movement is irrelevant. For example, if you drap&drop the file to itself in Windows, nothing happens. – Victor Zakharov Mar 10 '13 at 13:12
  • Imagine that you're creating the WinForms designer. Even if properly implemented, mouse movement is still relevant. It's an entirely different design than Windows Explorer where it doesn't make any sense to drop an icon on itself. But you can certainly drag a control a few pixels in any direction. Or a selection in a photo editor. Or any number of other cases where the "proper implementation" rule won't work. – Cody Gray - on strike Mar 10 '13 at 13:23
  • In those cases you would use keyboard positioning (arrow keys). It does exactly that - move in the designated direction by 1 pixel. Otherwise you would want, for example in VS, the control to stick to either form grid, or other controls. In both cases, VS will assist you with positioning. In photoshop or paint you can 1) use arrow keys or 2) have it zoomed down to the point when moving by several pixels in either direction becomes irrelevant. – Victor Zakharov Mar 10 '13 at 13:48
  • Alright, I see your point. Suppose we have a user with shaky hands, being a developer in Visual Studio. They aligned controls exactly `X` pixels underneath each other. They prefer to double-click the control to access its code-behind. So you introduce this 5px drag delay, so that they don't accidentally drag and break their `exactly X pixels` pattern. This is a rather artificial use case, but nevertheless possible with today's world. So yes, I agree, for this situation it may be useful. Graphical designers usually don't have this drag delay. It is assumed that you must have accurate fingers. – Victor Zakharov Mar 10 '13 at 14:06
  • 1
    How would I best go about this? Have a MouseUp negate the check? What if the mouse does up someplace else or something else registers the event? I'm always paranoid about cases like this – Ben Philipp May 30 '16 at 22:42
  • 2
    For anyone in the future checking this out, these properties *are* actually exposed through a .NET interface. Try using [SystemParameters.MinimumHorizontalDragDistance](https://msdn.microsoft.com/en-us/library/system.windows.systemparameters.minimumhorizontaldragdistance(v=vs.110).aspx) and [SystemParameters.MinimumVerticalDragDistance](https://msdn.microsoft.com/en-us/library/system.windows.systemparameters.minimumverticaldragdistance(v=vs.110).aspx), although they just use the same `GetSystemMetrics()` call shown above. Just some syntactical sugar. – Patrick Bell Jan 23 '17 at 17:49
5

I found this solution, although it is for a double-click and a mouse down events:

void pictureBox_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && e.Clicks ==1)
{
PictureBox pb = (PictureBox)sender;
DoDragDrop((ImageData)pb.Tag, DragDropEffects.Copy);
}
}

source: http://code.rawlinson.us/2007/04/c-dragdrop-and-doubleclick.html

2

Unfortunatelly, at the point of time when "button-is-pressed" you don't know yet if the desired action is just a click or a drag-drop. You will find it out it later.

For a click, the determinant is "no movement" and "button up".

For a drag, the determinant is "movement" and "button up".

Hence, to disambiguate those interactions, you have to track not only the buttons, but also the movement. You do not need to track the overall movement, only the movement between button-down and button-up is interesting.

Those events are therefore a good place to start/stop the Mouse.Capture mechanisms (to dynamically present drag adorners and drop location hints), or, in simplier form - to store the origin and target of movement vector and check if the distance is > D (even if movement occurred, there should be some safe minimal distance within which the DRAG is canceleed. The mouse is "jaggy" sometimes, and people would really don't like your app to start dragging when they double click at the end of fast mouse pointer movement :) )

quetzalcoatl
  • 32,194
  • 8
  • 68
  • 107