Thursday, November 25, 2010

WPF - Windows' forceful release of mouse capture

In this post, let us discuss about forceful release of mouse capture by Windows in WPF application. We will also be discussing how to get notified about such occurences.

Let us define a Window with two textboxes. We will be updating the two textboxes based on the values of X and Y coordinates of Mouse if it is captured.

<Window x:Class="MouseCaptureExample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<TextBox Height="25" HorizontalAlignment="Left" Margin="57,12,0,0"
Name="textBoxX" VerticalAlignment="Top" Width="73" />
<TextBox Height="25" HorizontalAlignment="Left" Margin="57,43,0,0"
Name="textBoxY" VerticalAlignment="Top" Width="73" />
<Label Height="25" HorizontalAlignment="Left" Margin="8,10,0,0"
Name="label1" VerticalAlignment="Top" Width="43">X Pos</Label>
<Label Height="25" HorizontalAlignment="Left" Margin="8,41,0,0"
Name="label2" VerticalAlignment="Top" Width="43">Y Pos</Label>
</Grid>
</Window>

Let us override two methods OnMouseUp and OnMouseDown available in Window [Inherited]. We maintain a variable isMouseCaptured to have an idea if mouse is still captured. Initially, we keep it set as false. As user push the mouse button down OnMouseDown() method gets called which sets isMouseCaptured as true. Releasing mouse causes OnMouseUp() method to get called. We are also overriding OnMouseMove(). Here we check if mouse is still captured. If yes, then update the two text boxes with X and Y position of the mouse.

public partial class Window1 : Window
{
private bool isMouseCaptured = false;

public Window1()
{
InitializeComponent();
}

protected override void OnMouseDown(MouseButtonEventArgs e)
{
Mouse.Capture(this);
this.isMouseCaptured = true;

base.OnMouseDown(e);
}

protected override void OnMouseMove(MouseEventArgs e)
{
if (isMouseCaptured)
{
Point mousePosition = Mouse.GetPosition(this);

this.textBoxX.Text = mousePosition.X.ToString();
this.textBoxY.Text = mousePosition.Y.ToString();
}

base.OnMouseMove(e);
}

protected override void OnMouseUp(MouseButtonEventArgs e)
{
Mouse.Capture(null);
this.isMouseCaptured = false;
base.OnMouseUp(e);
}
}

Now click mouse and move keeping the mouse button pressed. On keyboard hit ALT + TAB and select some other application. Leave mouse button this other application. Go back to our wpf application and notice the text boxes still being updated. Since we have release mouse button, we are expecting that mouse move should not cause updating the text boxes values.

Let me explain what has happened. After Hitting ALT + TAB, Windows has forcefully claimed mouse capture from the element. Since it is not capturing mouse anymore so it can not receive mouse events outside itself. That is why releasing mouse outside has not cause OnMouseUp overriden method to be called.

Now this could cause major problem for the application. Some drag operation might still be in progress which could resume as the mouse pointer enters the application.
Some drawing might be drawn and might cause havoc by this mouse enter. So this is a big issue for some applications.

How to fix it?
Luckily when windows claims mouse capture forcefully, WPF notifies the element about this event if the element subscribes with LostMouseCapture attached event. We should be updating our code like this:
public Window1()
{
InitializeComponent();

Mouse.AddLostMouseCaptureHandler(this, Window1_LostMouseCapture);
}

void Window1_LostMouseCapture(object sender, MouseEventArgs e)
{
this.isMouseCaptured = false;
}

In the above code, as the mouse capture is lost by the element, Window1_LostMouseCapture event is raised which sets isMouseCaptured as false. Alternatively, we could subscribe this event like this:
this.LostMouseCapture += new MouseEventHandler(Window1_LostMouseCapture);

This would stop the updates in the text boxes. When we run our application and drag the mouse the window keeps updating itself as follows:

1 comment:

Unknown said...

Hi Sir,

i use two screen(primary and secondary) in my wpf application.I want to change my window's max height based on the screen resolution.i want to know shall we can fire LostMouseCapture Event for screen?.If yes please help me to how to how to fire that event?