WPF Scrollable + Zoomable + Dragable Control

Unluckily there is no standard control to zoom, drag and scroll your content. My implementation is based on following tutorial. With this implementation you are able to zoom, drag and scroll arbitrary controls.

Showcase

ScrollDragZoomControl.xaml

<?xml version="1.0" encoding="UTF-8"?>
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="MinimalMonitoringClient.Controls.ScrollDragZoomControl" Background="Transparent">
   <UserControl.Template>
      <ControlTemplate TargetType="UserControl">
         <ScrollViewer x:Name="scrollViewer" Loaded="scrollViewer_Loaded" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
            <Grid Name="grid" RenderTransformOrigin="0.5,0.5">
               <Grid.LayoutTransform>
                  <TransformGroup>
                     <ScaleTransform x:Name="scaleTransform" />
                  </TransformGroup>
               </Grid.LayoutTransform>
               <Viewbox>
                  <!-- Present the actual stuff the user wants to display -->
                  <ContentPresenter />
               </Viewbox>
            </Grid>
         </ScrollViewer>
      </ControlTemplate>
   </UserControl.Template>
</UserControl>

ScrollDragZoomControl.xaml.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace NAMESPACE {
	/// <summary>
	/// Interaktionslogik für ScrollDragZoomControl.xaml
	/// </summary>
	public partial class ScrollDragZoomControl: UserControl {
		private int zoomLevel = 1;

		private ScaleTransform scaleTransform;
		private ScrollViewer scrollViewer;
		private Grid grid;

		Point ? lastCenterPositionOnTarget;
		Point ? lastMousePositionOnTarget;
		Point ? lastDragPoint;

		public ScrollDragZoomControl() {
			InitializeComponent();
		}

		private void scrollViewer_Loaded(object sender, RoutedEventArgs e) {
			scaleTransform = (ScaleTransform) Template.FindName("scaleTransform", this);
			scrollViewer = (ScrollViewer) Template.FindName("scrollViewer", this);
			grid = (Grid) Template.FindName("grid", this);

			scrollViewer.ScrollChanged += OnScrollViewerScrollChanged;
			scrollViewer.MouseLeftButtonUp += OnMouseLeftButtonUp;
			scrollViewer.PreviewMouseLeftButtonUp += OnMouseLeftButtonUp;
			scrollViewer.PreviewMouseWheel += OnPreviewMouseWheel;
			scrollViewer.PreviewMouseLeftButtonDown += OnMouseLeftButtonDown;
			scrollViewer.MouseMove += OnMouseMove;
		}

		void OnMouseMove(object sender, MouseEventArgs e) {
			if (lastDragPoint.HasValue) {
				Point posNow = e.GetPosition(scrollViewer);

				double dX = posNow.X - lastDragPoint.Value.X;
				double dY = posNow.Y - lastDragPoint.Value.Y;

				lastDragPoint = posNow;

				scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset - dX);
				scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - dY);
			}
		}

		void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
			var mousePos = e.GetPosition(scrollViewer);
			if (mousePos.X <= scrollViewer.ViewportWidth && mousePos.Y < scrollViewer.ViewportHeight) //make sure we still can use the scrollbars
			{
				scrollViewer.Cursor = Cursors.SizeAll;
				lastDragPoint = mousePos;
				Mouse.Capture(scrollViewer);
			}
		}

		void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e) {
			lastMousePositionOnTarget = Mouse.GetPosition(grid);

			if (e.Delta > 0) zoomLevel++;
			else if (e.Delta < 0) zoomLevel--;

			zoomLevel = zoomLevel < 1 ? 1 : zoomLevel;

			scaleTransform.ScaleX = zoomLevel;
			scaleTransform.ScaleY = zoomLevel;

			var centerOfViewport = new Point(scrollViewer.ViewportWidth / 2, scrollViewer.ViewportHeight / 2);
			lastCenterPositionOnTarget = scrollViewer.TranslatePoint(centerOfViewport, grid);

			e.Handled = true;
		}

		void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e) {
			scrollViewer.Cursor = Cursors.Arrow;
			scrollViewer.ReleaseMouseCapture();
			lastDragPoint = null;
		}

		void OnScrollViewerScrollChanged(object sender, ScrollChangedEventArgs e) {
			if (e.ExtentHeightChange != 0 || e.ExtentWidthChange != 0) {
				Point ? targetBefore = null;
				Point ? targetNow = null;

				if (!lastMousePositionOnTarget.HasValue) {
					if (lastCenterPositionOnTarget.HasValue) {
						var centerOfViewport = new Point(scrollViewer.ViewportWidth / 2, scrollViewer.ViewportHeight / 2);
						Point centerOfTargetNow = scrollViewer.TranslatePoint(centerOfViewport, grid);

						targetBefore = lastCenterPositionOnTarget;
						targetNow = centerOfTargetNow;
					}
				}
				else {
					targetBefore = lastMousePositionOnTarget;
					targetNow = Mouse.GetPosition(grid);

					lastMousePositionOnTarget = null;
				}

				if (targetBefore.HasValue) {
					double dXInTargetPixels = targetNow.Value.X - targetBefore.Value.X;
					double dYInTargetPixels = targetNow.Value.Y - targetBefore.Value.Y;

					double multiplicatorX = e.ExtentWidth / grid.Width;
					double multiplicatorY = e.ExtentHeight / grid.Height;

					double newOffsetX = scrollViewer.HorizontalOffset - dXInTargetPixels * multiplicatorX;
					double newOffsetY = scrollViewer.VerticalOffset - dYInTargetPixels * multiplicatorY;

					if (double.IsNaN(newOffsetX) || double.IsNaN(newOffsetY)) {
						return;
					}

					scrollViewer.ScrollToHorizontalOffset(newOffsetX);
					scrollViewer.ScrollToVerticalOffset(newOffsetY);
				}
			}
		}
	}
}

Usage

Add the “ScrollDragZoomControl” to your project and use it as follows:

    <controls:ScrollDragZoomControl>
        <!-- Your Content to present -->
    </controls:ScrollDragZoomControl>
0 0 vote
Article Rating
Subscribe
Notify of
0 Comments
Inline Feedbacks
View all comments