Pages

Tuesday, May 21, 2013

Mouse click and drag

This is a little bugger I coped with recently. In Windows forms dragging (holding a mouse button and moving the mouse) produces mouse click event after the mouse button is released. That's OK when a program is interested in only drag or click events but when it has to handle both events, each with different logic then there has to be a way to differentiate between those two events. Let's start with simple case where only mouse click is needed (C#-ish and .Net-ish code):

void init(){
    control.MouseClick.Add(clickHandler);
}

void clickHandler(object sender, EventArgs e)
{
    // Click logic
}

Now let see simple drag handler:

Point lastPos = null;
 
void init(){
    control.MouseMove.Add(moveHandler);
}

void moveHandler(object sender, MouseEventArgs e)
{
    if (lastPos == null)
        lastPos = e.Location;

    if (e.Button.HasFlag(MouseButtons.Left))
    {

        Point change = (e.Location - lastPos);
 

        // Drag logic
    }

    lastPos = e.Location;



There is no need to handle the situations when mouse leaves or enters control's area because once mouse is pressed over some control, only that control will receive mouse related events. That greatly simplifies the code for handling the mouse movement. Only overhead is checking if last position has been initialized. I hope it work the same on Linux and Mac OS with Mono.

If those two pieces of code were combined, mouse drag that ends inside starting control will trigger click handler. One solution is to simply track how far was mouse dragged:

Point? lastPos = null;
double dragDist = 0;
 
void init(){
    control.MouseClick.Add(clickHandler);
    control.MouseMove.Add(moveHandler);
}

void clickHandler(object sender, EventArgs e)
{
    if (dragDist > 0)
        return;


    // Click logic


void moveHandler(object sender, MouseEventArgs e)
{
    if (!lastPos.HasValue)
        lastPos = e.Location;

    if (e.Button.HasFlag(MouseButtons.Left))
    {

        Point change = (e.Location - (Size)lastPos.Value);
        dragDist += Math.Abs(change.X) + Math.Abs(change.Y);

        // Drag logic
    }
    else
        dragDist = 0;

    lastPos = e.Location;

}

Well, Euclidean distance would do too. I used Manhattan distance because the line of code is narrower and has less chance of being broken to multiple lines in your browser. Neat thing about this solutions is that border between click and drag can be adjusted to tolerate some mouse movement.