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:
- First, install Paint.NET or download the portable version, and then launch it. https://github.com/paintdotnet/release
- 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.
- Also make sure that the floating windows (Tools, History, Layers, Colors) are visible (not hidden). This is their default state.
- 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.
- Move the mouse a bit so that you have a selection of pretty much any size. Don't release the left mouse button yet.
- 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
- 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.
Background and motivation
Over in
Control.WmMouseUp(), mouse capture is always relinquished in afinallyblock at the end:winforms/src/System.Windows.Forms/System/Windows/Forms/Control.cs
Line 11596 in 8158b01
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_MOUSEUPmyself and redo all ofControl.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:
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
Captureis not set tofalseunless 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
Hand-wavey implementation:
API Usage
In my code I would do:
Alternative Designs
Maybe with a virtual method to permit full customization of what happens.
Risks
Because
WmMouseUpintersects 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 inControlwhereCapturewould need to be set tofalseif, for instance, aWM_MOUSEUPwas 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.