Skip to content

Need ability to not always release mouse capture at end of WmMouseUp for multi-button gestures #13126

Description

@rickbrew

Background and motivation

Over in Control.WmMouseUp(), mouse capture is always relinquished in a finally block at the end:

        finally
        {
            // Always reset the States.DoubleClickFired in UP. Since we get UP - DOWN - DBLCLK - UP sequence
            // The flag is set in L_BUTTONDBLCLK in the controls WndProc().
            SetState(States.DoubleClickFired, false);
            SetState(States.MousePressed, false);
            SetState(States.ValidationCancelled, false);

            // Capture is reset while exiting MouseUp.
            Capture = false;
        }

I have a very old bug in Paint.NET that is a result of this, and which I can't work around unless I want to completely handle WM_MOUSEUP myself and redo all of Control.WmMouseUp()'s logic. Which I don't think is even possible, as it's doing internal/private bookkeeping and whatnot.

The bug is a little weird but is something that a power user would trip over regularly:

  1. First, install Paint.NET or download the portable version, and then launch it. https://github.com/paintdotnet/release
  2. Make sure you're zoomed in enough so that the canvas extends beyond the bounds of the viewport (this isn't critical it just makes it easier). You can press Ctrl + + several times to do this.
  3. Also make sure that the floating windows (Tools, History, Layers, Colors) are visible (not hidden). This is their default state.
  4. Start drawing a selection with the Rectangle Select tool (it's the first tool in the Tools window). This is done by clicking and dragging with the left mouse button.
  5. Move the mouse a bit so that you have a selection of pretty much any size. Don't release the left mouse button yet.
  6. While still holding down the left mouse button, also click and hold down the right mouse button. This invokes the "move selection while drawing it" state
  7. Move the mouse so that it ends up over a floating window. (at this point you still have both the left and right mouse buttons pressed)

Expected: The canvas maintains mouse capture while the mouse is over the floating window. (Conceptually you can really think of the mouse as being "under" the floating window due to capture)

Actual: The canvas loses focus and selection drawing immediately stops. This is because the floating windows will take focus when the mouse is over them and the mouse is not captured elsewhere.


My proposal is to have some way to configure this, perhaps by making it so that Capture is not set to false unless all mouse buttons are arriving at the released state. This is the only scenario I have for this so I'm not sure if I have a good idea of a general purpose API. I am fine with a very narrowly tailored way of accomplishing this, whether with a property or a virtual method of some kind.

API Proposal

public class Control
{
    // This is a terrible name please come up with something better
    bool OnlyReleaseCaptureWhenAllMouseButtonsAreReleased
    {
        get;
        set;
    } = false;
}

Hand-wavey implementation:

public class Control
{
    ...
    /// <summary>
    ///  Handles the WM_MOUSEUP message.
    /// </summary>
    private void WmMouseUp(ref Message m, MouseButtons button, int clicks)
    {
        ...
        finally
        {
            // **** I'm not sure if any of these needs to be adjusted
            // Always reset the States.DoubleClickFired in UP. Since we get UP - DOWN - DBLCLK - UP sequence
            // The flag is set in L_BUTTONDBLCLK in the controls WndProc().
            SetState(States.DoubleClickFired, false);
            SetState(States.MousePressed, false);
            SetState(States.ValidationCancelled, false);

            // **** Need a new if() statement here
            if (!OnlyReleaseCaptureWhenAllMouseButtonsAreReleased || AllMouseButtonsAreReleased())
            {
                Capture = false;
            }
        }        
    }    
}

API Usage

In my code I would do:

internal sealed class CanvasControl : ...
{
    public CanvasControl()
    {
        ...
        this.OnlyReleaseCaptureWhenAllMouseButtonsAreReleased = true;
        ...
    }
}

Alternative Designs

Maybe with a virtual method to permit full customization of what happens.

    ...
    /// <summary>
    ///  Handles the WM_MOUSEUP message.
    /// </summary>
    private void WmMouseUp(ref Message m, MouseButtons button, int clicks)
    {
        ...
        finally
        {
            ...
            OnEndMouseUp();
        }        
    }

    protected virtual void OnEndMouseUp()
    {
        Capture = false;
    }
}

Risks

Because WmMouseUp intersects with many facets of mouse behavior including clicking and double-clicking, care should be taken to not break things in order to enable this scenario. There may be other locations in Control where Capture would need to be set to false if, for instance, a WM_MOUSEUP was not received due to some other state transition.

Will this feature affect UI controls?

Yes, this would likely need to be surfaced in the designer just like most other mutable properties.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

api-suggestion(1) Early API idea and discussion, it is NOT ready for implementation

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions