Zooming and panning in Windows Forms with Fixed focus
Introduction
I have found quite a few articles about zooming into a fixed point. I have also tried many solutions, but none of them worked. Many of them resized the PictureFrame so that it eventually would overlap other parts of the screen and none of them could keep the focus point. I then created my own solution that actually works. When zooming it does keep the same zoom point at all zoom levels.
Background
This image viewer was a small part of an selfencrypting, selfcontained image viewer, where I needed a simple image viewer that could zoom at the point I was looking at.
Using the code
A brief description of how to use the article or code. The class names, the methods and properties, any tricks or tips.
The Code is quite simple. The big problem was to hold track of the offset when zooming so I could move the image to the correct position to hold the focus point.
A few eventhandlers to catch some mouseevents
this.pictureBox.MouseDown += new System.Windows.Forms.MouseEventHandler(this.imageBox_MouseDown);
this.pictureBox.MouseMove += new System.Windows.Forms.MouseEventHandler(this.pictureBox_MouseMove);
this.pictureBox.MouseUp += new System.Windows.Forms.MouseEventHandler(this.imageBox_MouseUp);
ImageZoomMainForm initializes the windows, and sets the initial zoom factor. By default it zooms the image so it is as wide as the view.
First I declare some global variables to store the Image and keep track of zoom and the current offset of the image.
Image img;
Point mouseDown;
int startx = 0; // offset of image when mouse was pressed
int starty = 0;
int imgx = 0; // current offset of image
int imgy = 0;
bool mousepressed = false; // true as long as left mousebutton is pressed
float zoom = 1;
ImageZoomMainForm initializes the windows, opens the image and sets the initial zoom factor. By default it zooms the image so it is as wide as the view. I have to take into account that the screen and the Image can have different resolution. It did take me quite a long time to figure out why my fully zoomed windows wasn't zoomed, until I realised that the screen had a 96 resolution and the images was in 300.
public ImageZoomMainForm()
{
InitializeComponent();
string imagefilename = @"..\..\test.tif";
img = Image.FromFile(imagefilename);
Graphics g = this.CreateGraphics();
//// Fit whole image
//zoom = Math.Min(
// ((float)pictureBox.Height / (float)img.Height) * (img.VerticalResolution / g.DpiY),
// ((float)pictureBox.Width / (float)img.Width) * (img.HorizontalResolution / g.DpiX)
//);
// Fit width
zoom = ((float)pictureBox.Width / (float)img.Width) * (img.HorizontalResolution / g.DpiX);
pictureBox.Paint += new PaintEventHandler(imageBox_Paint);
}
MouseMove, MouseDown and MouseUp takes care of the panning. MouseDown records the starting position, and MouseMove then moves the image box accordingly. MouseUp just sets mousepressed to false so that I know that the button has been released.
private void pictureBox_MouseMove(object sender, EventArgs e)
{
MouseEventArgs mouse = e as MouseEventArgs;
if (mouse.Button == MouseButtons.Left)
{
Point mousePosNow = mouse.Location;
int deltaX = mousePosNow.X - mouseDown.X; // the distance the mouse has been moved since mouse was pressed
int deltaY = mousePosNow.Y - mouseDown.Y;
imgx = (int)(startx + (deltaX / zoom)); // calculate new offset of image based on the current zoom factor
imgy = (int)(starty + (deltaY / zoom));
pictureBox.Refresh();
}
}
private void imageBox_MouseDown(object sender, EventArgs e)
{
MouseEventArgs mouse = e as MouseEventArgs;
if (mouse.Button == MouseButtons.Left)
{
if (!mousepressed)
{
mousepressed = true;
mouseDown = mouse.Location;
startx = imgx;
starty = imgy;
}
}
}
private void imageBox_MouseUp(object sender, EventArgs e)
{
mousepressed = false;
}
OnMouseWheel does the actual zooming. It calculates where in the Image the mouse is pointing, calculate the new zoomfactor, make a new calculation, and then sets the offset accordingly. Paint does the transform. It resizes the image and moves it to the correct location.
protected override void OnMouseWheel(MouseEventArgs e)
{
float oldzoom = zoom;
if (e.Delta > 0)
{
zoom += 0.1F;
}
else if (e.Delta < 0)
{
zoom = Math.Max(zoom - 0.1F, 0.01F);
}
MouseEventArgs mouse = e as MouseEventArgs;
Point mousePosNow = mouse.Location;
int x = mousePosNow.X - pictureBox.Location.X; // Where location of the mouse in the pictureframe
int y = mousePosNow.Y - pictureBox.Location.Y;
int oldimagex = (int)(x / oldzoom); // Where in the IMAGE is it now
int oldimagey = (int)(y / oldzoom);
int newimagex = (int)(x / zoom); // Where in the IMAGE will it be when the new zoom i made
int newimagey = (int)(y / zoom);
imgx = newimagex - oldimagex + imgx; // Where to move image to keep focus on one point
imgy = newimagey - oldimagey + imgy;
pictureBox.Refresh(); // calls imageBox_Paint
}
private void imageBox_Paint(object sender, PaintEventArgs e)
{
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
e.Graphics.ScaleTransform(zoom, zoom);
e.Graphics.DrawImage(img, imgx, imgy);
}
ProcessCmdKey overrules the default method. It makes it possible to move around in the image using the arrowkeys and PgUp and PgDown. Arrowkeys moves 10%, PgUp and PgDown moves 90% of the screensize.
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
const int WM_KEYDOWN = 0x100;
const int WM_SYSKEYDOWN = 0x104;
if ((msg.Msg == WM_KEYDOWN) || (msg.Msg == WM_SYSKEYDOWN))
{
switch (keyData)
{
case Keys.Right:
imgx -= (int)(pictureBox.Width * 0.1F / zoom);
pictureBox.Refresh();
break;
case Keys.Left:
imgx += (int)(pictureBox.Width * 0.1F / zoom);
pictureBox.Refresh();
break;
case Keys.Down:
imgy -= (int)(pictureBox.Height * 0.1F / zoom);
pictureBox.Refresh();
break;
case Keys.Up:
imgy += (int)(pictureBox.Height * 0.1F / zoom);
pictureBox.Refresh();
break;
case Keys.PageDown:
imgy -= (int)(pictureBox.Height * 0.90F / zoom);
pictureBox.Refresh();
break;
case Keys.PageUp:
imgy += (int)(pictureBox.Height * 0.90F / zoom);
pictureBox.Refresh();
break;
}
}
return base.ProcessCmdKey(ref msg, keyData);
}
}
}
Points of Interest
History
Initial release
发表评论
HXN7gd wow, awesome blog article.Much thanks again. Awesome.
Tjnp2R Really appreciate you sharing this blog article.Really thank you! Want more.