0
votes

There is something about WPF mouse handling I obviously do not understand, so I’ll appreciate any help I can get. Sorry for making this lengthy – I’ve been told to provide enough context info so others can figure out what I am trying to accomplish.

I have a series of buttons that each contain the preview of an image. When zooming into a full image that I also display, I’d like to use the corresponding preview button as an indicator of which part of the full image I am actually zooming into. For that, I use an Adorner that draws a simple rectangular box on top of the preview image, reflecting the zoomed area. Please see the screenshot for clarification – the button at the bottom left shows the zoomed area in an orange rectangle.

Screenshot

All of that works well already, and if I move the zoomed area around by clicking the full image and moving it, the orange box moves correspondingly as it should.

Now here’s my issue: I’d also like to be able to move the main zoom area around by putting the mouse down within the orange box on the preview button and then moving the box around. The way I am trying to implement this, I give the preview button event handlers for PreviewMouseDown, PreviewMouseUp and MouseMove events like this:

Button prevBut = new Button(); 
prevBut.PreviewMouseDown += PrevBut_PreviewMouseDown; 
prevBut.PreviewMouseUp += PrevBut_PreviewMouseUp; 
prevBut.MouseMove += PrevBut_MouseMove;

PreviewMouseDown checks whether the mouse position is actually within the orange box. If it is, it records the start position and captures the mouse for the button. PreviewMouseUp releases the mouse capture again. MouseMove should now get called and do some stuff to update the zoom depending on the mouse position respectively the change from the start position.

private void PrevBut_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
    MouseStartPosRelativeToPreviewButton = e.GetPosition((Button)sender);

    if ( [some calculation to verify that mouse position is within orange box] )
        ((Button)sender).CaptureMouse();
}


private void PrevBut_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
    ((Button)sender).ReleaseMouseCapture();
}


private void PrevBut_MouseMove(object sender, MouseEventArgs e)
{
    if (((Button)sender).IsMouseCaptured)
    {
        Point PosRelativeToPreviewImage = e.MouseDevice.GetPosition((Button)sender);

        ..  [Do some calculations and update zoom plus the position of the orange box]
    }
}

Problem is, PrevBut_MouseMove() gets called regularly when the mouse is over the button (I verified that) but the calls STOP when a mouse button is pressed. In other words, if I log what happens, I see plenty of PrevBut_MouseMove() calls when moving the mouse above the button, then when I click anywhere on the button, I get PrevBut_PreviewMouseDown(), directly followed by PrevBut_PreviewMouseUp() (once I released the mouse). Even if I keep the mouse button pressed for several seconds, not a single move gets recorded. Once I released the mouse button, there are plenty more PrevBut_MouseMove() calls until the mouse leaves the button. As I said, I don’t understand this. Why would MouseMove no longer fire when a mouse button is active?

The Adorner (orange box) is set to IsHitTestVisible = false, by the way, so as I understand it, it should not get in the way of mouse events. (Or could it?) I already spent many hours trying to find a workaround and could not make it work. Will appreciate any help you can give me. Thanks.

1

1 Answers

0
votes

This is the normal behavior. The MouseMove event is still raised by the input manager. But since events and their registered handlers are executed synchronously (WPF doesn't implement asynchronous events), the MouseMove handlers can only execute after all currently executing handlers e.g., MouseDown have returned. This is why MouseDown interrupts MouseMove (for the duration of all registered event handlers).

If you are interested primarily in MouseMove and would like to react to the mouse buttons too (while the mouse is moving), you should handle those buttons inside the handler of MouseMove.
You can do this by polling the button's state during the sequence of MouseMove events by accessing the MouseEventArgs:

// Track pressed mouse buttons to determine that the MouseButtonState.Released state
// immediately follows the MouseButtonState.Pressed button state
private bool IsMouseButtonPreviouslyPressed  { get; sete; }

private void OnMouseMove(object sender, MouseEventArgs e)
{
  Point currentMousePointerPosition = e.GetPosition(sender as IInputElement);
  var eventSource = sender as UIElement;

  if (e.LeftButton == MouseButtonState.Pressed)
  {
    IsMouseButtonPreviouslyPressed  = true;
    HandleLeftMouseButtonDown(currentMousePointerPosition, eventSource);
  }
  if (IsMouseButtonPreviouslyPressed  && e.LeftButton == MouseButtonState.Released)
  {
    IsMouseButtonPreviouslyPressed  = false;
    HandleLeftMouseButtonUp(currentMousePointerPosition, eventSource);
  }
  HandleMouseMove(currentMousePointerPosition, eventSource);
}

private void HandleLeftMouseButtonDown(Point mousePointerPosition, UIElement sourceElement)
{
    if ( [some calculation to verify that mouse position is within orange box] )
    {
      sourceElement.CaptureMouse();
    }
}

private void HandleLeftMouseButtonUp(Point mousePointerPosition, UIElement sourceElement)
{
    if ( [some calculation to verify that mouse position is within orange box] )
    {
      sourceElement.ReleaseMouseCapture();
    }
}

private void HandleMouseMove(Point mousePointerPosition, UIElement sourceElement)
{
    if (sourceElement.IsMouseCaptured)
    {
      ..  [Do some calculations and update zoom plus the position of the orange box]
    }
}