Custom MouseNavigation

Mar 28, 2011 at 1:52 PM
Edited Mar 28, 2011 at 1:53 PM

Hi

I was needed to implement a application conformant mouse navigation. This was not possible with the MouseNavigation class provided. I therefor changed the class, in order to make it more flexible. Maybe you want to reflect this changes in your code. I've changed it in a way, the current behaviour will not change, if the new methods are not overloaded. Here is a list of the the changes I've made:

  • There are new Methods checking if panning and zooming has to be continued/stopped.
  • There is now a difference between zooming and zooming an area.
  • Zooming can now be implemented in MouseMove

You will find the new code below.

Best regards, Torsten

using System;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
using Microsoft.Research.DynamicDataDisplay;
using Microsoft.Research.DynamicDataDisplay.Navigation;

namespace KukaRoboter.ProcessValueEditor.ComponentUI.Editor
{
   /// <summary>Provides common methods of mouse navigation around viewport</summary>
   public class MouseNavigationFixed : NavigationBase
   {
      private AdornerLayer adornerLayer;
      protected AdornerLayer AdornerLayer
      {
         get
         {
            if (adornerLayer == null)
            {
               adornerLayer = AdornerLayer.GetAdornerLayer(this);
               if (adornerLayer != null)
               {
                  adornerLayer.IsHitTestVisible = false;
               }
            }

            return adornerLayer;
         }
      }

      public override void OnPlotterAttached(Plotter plotter)
      {
         base.OnPlotterAttached(plotter);

         Mouse.AddPreviewMouseDownHandler(Parent, OnMouseDown);
         Mouse.AddPreviewMouseMoveHandler(Parent, OnMouseMove);
         Mouse.AddPreviewMouseUpHandler(Parent, OnMouseUp);
         Mouse.AddPreviewMouseWheelHandler(Parent, OnMouseWheel);
      }

      public override void OnPlotterDetaching(Plotter plotter)
      {
         Mouse.RemovePreviewMouseDownHandler(Parent, OnMouseDown);
         Mouse.RemovePreviewMouseMoveHandler(Parent, OnMouseMove);
         Mouse.RemovePreviewMouseUpHandler(Parent, OnMouseUp);
         Mouse.RemovePreviewMouseWheelHandler(Parent, OnMouseWheel);

         base.OnPlotterDetaching(plotter);
      }

      private void OnMouseWheel(object sender, MouseWheelEventArgs e)
      {
         if (!e.Handled)
         {
            HandleMouseWheel(e);

            e.Handled = true;
         }
      }

      protected void HandleMouseWheel(MouseWheelEventArgs e)
      {
         int delta = -e.Delta;
         Point mousePos = e.GetPosition(this);
         MouseWheelZoom(mousePos, delta);
      }

#if DEBUG
      public override string ToString()
      {
         if (!String.IsNullOrEmpty(Name))
         {
            return Name;
         }
         return base.ToString();
      }
#endif

      bool adornerAdded;
      RectangleSelectionAdorner selectionAdorner;
      private void AddSelectionAdorner()
      {
         if (!adornerAdded)
         {
            AdornerLayer layer = AdornerLayer;
            if (layer != null)
            {
               selectionAdorner = new RectangleSelectionAdorner(this) { Border = zoomRect };

               layer.Add(selectionAdorner);
               adornerAdded = true;
            }
         }
      }

      private void RemoveSelectionAdorner()
      {
         AdornerLayer layer = AdornerLayer;
         if (layer != null)
         {
            layer.Remove(selectionAdorner);
            adornerAdded = false;
         }
      }

      private void UpdateSelectionAdorner()
      {
         selectionAdorner.Border = zoomRect;
         selectionAdorner.InvalidateVisual();
      }

      Rect? zoomRect;
      private const double wheelZoomSpeed = 1.2;
      private bool shouldKeepRatioWhileZooming;

      private bool isZooming;
      protected bool IsZooming
      {
         get { return isZooming; }
      }

      private bool isZoomingArea;
      protected bool IsZoomingArea
      {
         get { return isZoomingArea; }
      }

      private bool isPanning;
      protected bool IsPanning
      {
         get { return isPanning; }
      }

      private Point panningStartPointInViewport;
      protected Point PanningStartPointInViewport
      {
         get { return panningStartPointInViewport; }
      }

      private Point zoomStartPoint;

      private static bool IsShiftOrCtrl
      {
         get
         {
            ModifierKeys currKeys = Keyboard.Modifiers;
            return (currKeys | ModifierKeys.Shift) == currKeys ||
               (currKeys | ModifierKeys.Control) == currKeys;
         }
      }

      protected virtual bool ShouldStartPanning(MouseButtonEventArgs e)
      {
         return e.ChangedButton == MouseButton.Left && Keyboard.Modifiers == ModifierKeys.None;
      }

      protected virtual bool ShouldContinuePanning(MouseEventArgs e)
      {
         return e.LeftButton == MouseButtonState.Pressed;
      }

      protected virtual bool ShouldStopPanning(MouseButtonEventArgs e)
      {
         return false;
      }

      protected virtual bool ShouldStartZoom(MouseButtonEventArgs e)
      {
         return false;
      }

      protected virtual bool ShouldContinueZoom(MouseEventArgs e)
      {
         return false;
      }

      protected virtual bool ShouldStopZoom(MouseButtonEventArgs e)
      {
         return e.LeftButton == MouseButtonState.Released;
      }

      protected virtual bool ShouldStartZoomArea(MouseButtonEventArgs e)
      {
         return e.ChangedButton == MouseButton.Left && IsShiftOrCtrl;
      }

      protected virtual bool ShouldContinueZoomArea(MouseEventArgs e)
      {
         return e.LeftButton == MouseButtonState.Pressed;
      }

      protected virtual bool ShouldStopZoomArea(MouseButtonEventArgs e)
      {
         return e.LeftButton == MouseButtonState.Released;
      }

      Point panningStartPointInScreen;
      protected virtual void StartPanning(MouseButtonEventArgs e)
      {
         panningStartPointInScreen = e.GetPosition(this);
         panningStartPointInViewport = panningStartPointInScreen.ScreenToViewport(Viewport.Transform);

         Plotter2D.UndoProvider.CaptureOldValue(Viewport, Viewport2D.VisibleProperty, Viewport.Visible);

         Cursor = Cursors.ScrollAll;

         isPanning = true;
         CaptureMouse();
      }

      protected virtual void StartZoom(MouseButtonEventArgs e)
      {
      }

      protected virtual void StartZoomArea(MouseButtonEventArgs e)
      {
         zoomStartPoint = e.GetPosition(this);
         if (Viewport.Output.Contains(zoomStartPoint))
         {
            isZooming = true;
            AddSelectionAdorner();
            CaptureMouse();
            shouldKeepRatioWhileZooming = Keyboard.Modifiers == ModifierKeys.Shift;
         }
      }

      private void OnMouseDown(object sender, MouseButtonEventArgs e)
      {
         // dragging
         bool shouldStartDrag = ShouldStartPanning(e);
         if (shouldStartDrag)
            StartPanning(e);

         // zooming
         bool shouldStartZoom = ShouldStartZoom(e);
         if (shouldStartZoom)
            StartZoom(e);

         // zooming area
         bool shouldStartZoomArea = ShouldStartZoom(e);
         if (shouldStartZoomArea)
            StartZoomArea(e);

         ((IInputElement)Parent).Focus();
      }

      private void OnMouseMove(object sender, MouseEventArgs e)
      {
         // dragging
         if (isPanning && ShouldContinuePanning(e))
         {
            HandlePanning(e);
         }

         // zooming
         if (isZooming && ShouldContinueZoom(e))
         {
            HandleZoom(e);
         }

         // zooming area
         if (isZoomingArea && ShouldContinueZoomArea(e))
         {
            HandleZoomArea(e);
         }
      }

      protected virtual void HandleZoomArea(MouseEventArgs e)
      {
         Point zoomEndPoint = e.GetPosition(this);
         UpdateZoomRect(zoomEndPoint);
      }

      protected virtual void HandleZoom(MouseEventArgs e)
      {
      }

      protected virtual void HandlePanning(MouseEventArgs e)
      {
         Point endPoint = e.GetPosition(this).ScreenToViewport(Viewport.Transform);

         Point loc = Viewport.Visible.Location;
         Vector shift = panningStartPointInViewport - endPoint;
         loc += shift;

         // preventing unnecessary changes, if actually visible hasn't change.
         if (shift.X != 0 || shift.Y != 0)
         {
            Rect visible = Viewport.Visible;

            visible.Location = loc;
            Viewport.Visible = visible;
         }
      }

      private static bool IsShiftPressed()
      {
         return Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
      }

      private void UpdateZoomRect(Point zoomEndPoint)
      {
         Rect output = Viewport.Output;
         Rect tmpZoomRect = new Rect(zoomStartPoint, zoomEndPoint);
         tmpZoomRect = Rect.Intersect(tmpZoomRect, output);

         shouldKeepRatioWhileZooming = IsShiftPressed();
         if (shouldKeepRatioWhileZooming)
         {
            double currZoomRatio = tmpZoomRect.Width / tmpZoomRect.Height;
            double zoomRatio = output.Width / output.Height;
            if (currZoomRatio < zoomRatio)
            {
               double oldHeight = tmpZoomRect.Height;
               double height = tmpZoomRect.Width / zoomRatio;
               tmpZoomRect.Height = height;
               if (!tmpZoomRect.Contains(zoomStartPoint))
               {
                  tmpZoomRect.Offset(0, oldHeight - height);
               }
            }
            else
            {
               double oldWidth = tmpZoomRect.Width;
               double width = tmpZoomRect.Height * zoomRatio;
               tmpZoomRect.Width = width;
               if (!tmpZoomRect.Contains(zoomStartPoint))
               {
                  tmpZoomRect.Offset(oldWidth - width, 0);
               }
            }
         }

         zoomRect = tmpZoomRect;
         UpdateSelectionAdorner();
      }

      private void OnMouseUp(object sender, MouseButtonEventArgs e)
      {
         OnParentMouseUp(e);
      }

      protected virtual void OnParentMouseUp(MouseButtonEventArgs e)
      {
         if (isPanning && ShouldStopPanning(e))
         {
            StopPanning(e);
         }
         
         if (isZooming && ShouldStopZoom(e))
         {
            StopZooming();
         }

         if (isZoomingArea && ShouldStopZoomArea(e))
         {
            StopZoomingArea();
         }
      }

      protected virtual void StopZooming()
      {
         isZooming = false;
      }

      protected virtual void StopZoomingArea()
      {
         if (zoomRect.HasValue)
         {
            Point p1 = zoomRect.Value.TopLeft.ScreenToViewport(Viewport.Transform);
            Point p2 = zoomRect.Value.BottomRight.ScreenToViewport(Viewport.Transform);
            Rect newVisible = new Rect(p1, p2);
            Viewport.Visible = newVisible;

            zoomRect = null;
            ReleaseMouseCapture();
            RemoveSelectionAdorner();

            isZoomingArea = false;
         }
      }

      protected virtual void StopPanning(MouseButtonEventArgs e)
      {
         isPanning = false;

         Plotter2D.UndoProvider.CaptureNewValue(Plotter2D.Viewport, Viewport2D.VisibleProperty, Viewport.Visible);

         Plotter2D.Focus();

         ReleaseMouseCapture();
         ClearValue(CursorProperty);
      }

      //protected override void OnRenderCore(DrawingContext dc, RenderState state)
      //{
      //    // do nothing here
      //}

      protected override void OnLostFocus(RoutedEventArgs e)
      {
         if (isZooming)
         {
            RemoveSelectionAdorner();
         }
         ReleaseMouseCapture();
         base.OnLostFocus(e);
      }

      private void MouseWheelZoom(Point mousePos, int wheelRotationDelta)
      {
         Point zoomTo = mousePos.ScreenToViewport(Viewport.Transform);

         double zoomSpeed = Math.Abs(wheelRotationDelta / Mouse.MouseWheelDeltaForOneLine);
         zoomSpeed *= wheelZoomSpeed;
         if (wheelRotationDelta < 0)
         {
            zoomSpeed = 1 / zoomSpeed;
         }
         Viewport.Visible = Viewport.Visible.Zoom(zoomTo, zoomSpeed);
      }
   }
}

Apr 5, 2011 at 7:42 PM

thanks !!!

can you post me a small example of how to use your code?

:)

Apr 6, 2011 at 7:50 AM

Hi

I have changed the HalloWorldSample so now you pan the content with the middle mouse button and you can zoom by holding the middle mouse button and clicking the right one. This is just some strange case. ;o)

You can download the sample here: http://www.file-upload.net/download-3339597/HelloWorld.zip.html

Regards, Torsten