Introduction
I have found quite a few articles about zooming into a fixed point. I have also tried many solutions, some worked, some did not. The ones that did work were too complicated to my needs. The ones that did not work could not hold focus or they overlapped other parts of the screen when zooming in. Another common problem was when panning. Many solutions did not record the original position of the mouse, so when you started panning, your image would make a sudden jump so you held on to the top left corner and not where your mouse was before. In my Windows Forms solution, one of the biggest issues was to limit the image size so that it wouldn't overlap other parts; in WPF, it was much easier as I could set the "cliptobounds
" to accomplish the same.
Using the code
After a lot of trials and errors, I ended up with a quite simple code.
A few event handlers to catch some mouse events:
WPFWindow.MouseWheel += MainWindow_MouseWheel;
image.MouseLeftButtonDown += image_MouseLeftButtonDown;
image.MouseLeftButtonUp += image_MouseLeftButtonUp;
image.MouseMove += image_MouseMove;
First, I declare some global variables. Start
to store the position of the mouse when the mouse button is first clicked, and origin
to store the original offset of the image before it was moved.
private Point origin; private Point start;
MouseDown
sets the two variables.
private void image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (image.IsMouseCaptured) return;
image.CaptureMouse();
start = e.GetPosition(border);
origin.X = image.RenderTransform.Value.OffsetX;
origin.Y = image.RenderTransform.Value.OffsetY;
}
MouseMove
takes care of the panning. I move the image around relative to the position saved in the MouseDown
event. I just set the offset to the difference between the original position of the mouse and the current position of the mouse, and add that value to the original offset.
private void image_MouseMove(object sender, MouseEventArgs e)
{
if (!image.IsMouseCaptured) return;
Point p = e.MouseDevice.GetPosition(border);
Matrix m = image.RenderTransform.Value;
m.OffsetX = origin.X + (p.X - start.X);
m.OffsetY = origin.Y + (p.Y - start.Y);
image.RenderTransform = new MatrixTransform(m);
}
MouseWheel
does the actual zooming. It uses ScaleAtPrepend
which not only does the zooming, but also keeps the focus. This was a lot easier than the Windows Forms version.
private void MainWindow_MouseWheel(object sender, MouseWheelEventArgs e)
{
Point p = e.MouseDevice.GetPosition(image);
Matrix m = image.RenderTransform.Value;
if (e.Delta > 0)
m.ScaleAtPrepend(1.1, 1.1, p.X, p.Y);
else
m.ScaleAtPrepend(1/1.1, 1/1.1, p.X, p.Y);
image.RenderTransform = new MatrixTransform(m);
}
Points of Interest
It is important to notice the "cliptobounds
" in the XAML. If it is true, it ensures that the image is held within the border and doesn't overlap other parts of the window.
<border grid.row="1" name="border" cliptobounds="True">
<img name="image" opacity="1"
source="/WPF%20Image%20Pan%20and%20Zoom;component/Images/test.tif" />
</border>
History