title: How to enable both scrolling and zooming using pinch in WPF?
body: <p>I am struggling with making both touch events and manipulation work properly in a WPF project. I have a <em>ScrollViewer</em> which contains a picture and I would like to scroll both horizontally and vertically using a swipe gestures. Additionally, I would like to zoom in/out in the center of the pinch gesture. The code below achieves what I wish, but it has the following problems:</p>
  
  <ul>
  <li>Sometimes the scrolling is laggy;</li>
  <li>The scrolling does not work on the first try, only when attempting the same gesture a second time;</li>
  <li>The zoom in/out does not work on the first try, only when attempting the same gesture a second time.</li>
  </ul>
  
  <p>I enabled the <em>IsManipulationEnabled</em> and I implemented the code for zoom in/out functionality. However, I was not able to combine it with the scrolling functionality (by setting the <em>PanningMode</em> in the <em>ScrollViewer</em> only). Therefore, I created a custom control which inherits from <em>Image</em> control and I overwritten the <strong>OnTouchDown</strong> and <strong>OnTouchUp</strong> event handlers. Basically, what I am doing in these overwritten handlers is counting the number of touches on the screen and enabling/disabling manipulation. I also tried setting the <em>PanningMode</em> for the <em>ScrollViewer</em>, but it did not do the trick.</p>
  
  <p>Below is the XAML:</p>
  
  <pre><code>&lt;Grid&gt;
          &lt;ScrollViewer
              x:Name="ScrollViewerParent"
              HorizontalScrollBarVisibility="Auto"
              VerticalScrollBarVisibility="Auto"
              PanningMode="Both"&gt;
              &lt;local:CustomImage 
                  x:Name="MainImage"
                  Source="{Binding Source={x:Static local:Constants.ImagePath}}"
                  IsManipulationEnabled="True"
                  ManipulationStarting="MainImage_ManipulationStarting"
                  ManipulationDelta="MainImage_ManipulationDelta"&gt;
              &lt;/local:CustomImage&gt;
          &lt;/ScrollViewer&gt;
      &lt;/Grid&gt;
  </code></pre>
  
  <p>Here is the code-behind:</p>
  
  <pre><code>public partial class MainWindow : Window
  {
          private void MainImage_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
          {
              e.ManipulationContainer = ScrollViewerParent;
              e.Handled = true;
          }
  
          private void MainImage_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
          {
              var matrix = MainImage.LayoutTransform.Value;
  
              Point? centerOfPinch = (e.ManipulationContainer as FrameworkElement)?.TranslatePoint(e.ManipulationOrigin, ScrollViewerParent);
  
              if (centerOfPinch == null)
              {
                  return;
              }
  
              var deltaManipulation = e.DeltaManipulation;
              matrix.ScaleAt(deltaManipulation.Scale.X, deltaManipulation.Scale.Y, centerOfPinch.Value.X, centerOfPinch.Value.Y);
              MainImage.LayoutTransform = new MatrixTransform(matrix);
  
              Point? originOfManipulation = (e.ManipulationContainer as FrameworkElement)?.TranslatePoint(e.ManipulationOrigin, MainImage);
  
              double scrollViewerOffsetX = ScrollViewerParent.HorizontalOffset;
              double scrollViewerOffsetY = ScrollViewerParent.VerticalOffset;
  
              double pointMovedOnXOffset = originOfManipulation.Value.X - originOfManipulation.Value.X * deltaManipulation.Scale.X;
              double pointMovedOnYOffset = originOfManipulation.Value.Y - originOfManipulation.Value.Y * deltaManipulation.Scale.Y;
  
              double multiplicatorX = ScrollViewerParent.ExtentWidth / MainImage.ActualWidth;
              double multiplicatorY = ScrollViewerParent.ExtentHeight / MainImage.ActualHeight;
  
              ScrollViewerParent.ScrollToHorizontalOffset(scrollViewerOffsetX - pointMovedOnXOffset * multiplicatorX);
              ScrollViewerParent.ScrollToVerticalOffset(scrollViewerOffsetY - pointMovedOnYOffset * multiplicatorY);
  
              e.Handled = true;
          }
  }
  </code></pre>
  
  <p>The XAML for the custom control:</p>
  
  <pre><code>&lt;Style TargetType="{x:Type local:CustomImage}" /&gt;
  </code></pre>
  
  <p>Here is where I override the OnTouchDown and OnTouchUp event handlers:</p>
  
  <pre><code> public class CustomImage : Image
      {
  
          private volatile int nrOfTouchPoints;
          private volatile bool isManipulationReset;
          private object mutex = new object();
  
          static CustomImage()
          {
              DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomImage), new FrameworkPropertyMetadata(typeof(CustomImage)));
          }
  
          protected override void OnTouchDown(TouchEventArgs e)
          {
              lock (mutex)
              {
                  nrOfTouchPoints++;
                  if (nrOfTouchPoints &gt;= 2)
                  {
                      IsManipulationEnabled = true;
                      isManipulationReset = false;
                  }
              }
              base.OnTouchDown(e);
          }
  
          protected override void OnTouchUp(TouchEventArgs e)
          {
              lock (mutex)
              {
                  if (!isManipulationReset)
                  {
                      IsManipulationEnabled = false;
                      isManipulationReset = true;
                      nrOfTouchPoints = 0;
                  }
              }
              base.OnTouchUp(e);
          }
      }
  </code></pre>
  
  <p>What I expect from this code is the following:</p>
  
  <ul>
  <li>When using one finger to swipe horizontally or vertically across the touchscreen, the image should be scrolled accordingly;</li>
  <li>When I use a pinch gesture on the touch screen, the image should be zoomed in/out in the center of the pinch.</li>
  </ul>