Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / Windows Forms

Zooming and panning in Windows Forms with fixed focus

Rate me:
Please Sign up or sign in to vote.
4.56/5 (21 votes)
14 Mar 2011CPOL2 min read 83.2K   5.5K   55   25
How to do zooming and panning in Windows Forms with fixed focus.

ZoomedOut.png

Introduction

I have found quite a few articles about zooming into a fixed point. I have also tried many solutions, but not all of them worked as they should. Many of them resized the PictureFrame so that it eventually would overlap other parts of the screen, others could not hold focus, and some others lost focus when panning the image. This solution does work. When panning, the image doesn't move when you grab hold of the image, and when zooming, it does keep the same zoom point at all zoom levels.

ZoomedIn.png

Background

This image viewer was a small part of a self-encrypting, self-contained image viewer, where I needed a simple image viewer that could zoom at the point I was pointing at.

Using the code

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 event handlers to catch some mouse events:

C#
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 the zoom and the current offset of the image.

C#
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 resolutions. 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 image's was 300.

C#
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 take 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.

C#
private void pictureBox_MouseMove(object sender, EventArgs e)
{
    MouseEventArgs mouse = e as MouseEventArgs;

    if (mouse.Button == MouseButtons.Left)
    {
        Point mousePosNow = mouse.Location;

        // the distance the mouse has been moved since mouse was pressed
        int deltaX = mousePosNow.X - mouseDown.X;
        int deltaY = mousePosNow.Y - mouseDown.Y;

        // calculate new offset of image based on the current zoom factor
        imgx = (int)(startx + (deltaX / zoom));
        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, calculates the new zoomfactor, make sa new calculation to figure out where the mouse will be positioned after the zoom, and then calculates where to move the image to hold the focus point. Paint does the transform. It resizes the image and moves it to the correct location.

C#
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;

    // Where location of the mouse in the pictureframe
    int x = mousePosNow.X - pictureBox.Location.X;
    int y = mousePosNow.Y - pictureBox.Location.Y;

    // Where in the IMAGE is it now
    int oldimagex = (int)(x / oldzoom);
    int oldimagey = (int)(y / oldzoom);

    // Where in the IMAGE will it be when the new zoom i made
    int newimagex = (int)(x / zoom);
    int newimagey = (int)(y / zoom);

    // Where to move image to keep focus on one point
    imgx = newimagex - oldimagex + imgx;
    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 arrow keys and PgUp and PgDown. Arrow keys move 10%, PgUp and PgDown move 90% of the screen size.

C#
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);
    }
}

History

  • Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) DSR
Denmark Denmark
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Praiseexcellent! Pin
Southmountain7-Sep-22 8:41
Southmountain7-Sep-22 8:41 
GeneralMy vote of 5 Pin
Martin Schnell5-Aug-21 10:19
Martin Schnell5-Aug-21 10:19 
QuestionSet visual objects over the image and use image as a background image map. Help. Pin
glbrt.gds26-Jan-21 5:44
glbrt.gds26-Jan-21 5:44 
QuestionMany thanks Pin
Neil_3-Sep-19 1:43
Neil_3-Sep-19 1:43 
PraiseThank you very much! Pin
Gabriel Mota28-May-18 8:43
Gabriel Mota28-May-18 8:43 
Questionperfect.... i search long time for this Pin
Datajohan24-Jul-17 6:42
professionalDatajohan24-Jul-17 6:42 
PraiseVild godt - Very well done Pin
N. Henrik Lauridsen16-Jun-17 21:15
N. Henrik Lauridsen16-Jun-17 21:15 
QuestionOnResize functionality Pin
Member 1239697517-Mar-16 7:12
Member 1239697517-Mar-16 7:12 
Questionmouse position relative to the image Pin
hayu16-Jul-14 22:46
hayu16-Jul-14 22:46 
AnswerRe: mouse position relative to the image Pin
BernhardDieber8-Jan-15 2:22
BernhardDieber8-Jan-15 2:22 
QuestionLittle help Pin
Vlade Ilievski28-Oct-13 1:27
Vlade Ilievski28-Oct-13 1:27 
AnswerRe: Little help Pin
defej12-Feb-14 18:45
defej12-Feb-14 18:45 
QuestionAlternative Pin
general.failure16-Apr-13 23:33
general.failure16-Apr-13 23:33 
Questioncode for zooming Pin
ayyappa75215-Mar-13 7:31
ayyappa75215-Mar-13 7:31 
AnswerRe: code for zooming Pin
Lars Pehrsson15-Mar-13 22:16
Lars Pehrsson15-Mar-13 22:16 
GeneralMy vote of 5 Pin
avnerma10-Feb-13 20:58
avnerma10-Feb-13 20:58 
QuestionRotate Image Pin
Faisal437-Aug-12 19:02
Faisal437-Aug-12 19:02 
QuestionRotate Image Pin
Faisal437-Aug-12 19:00
Faisal437-Aug-12 19:00 
QuestionZoom Speed Pin
Coen Klosters20-Jun-12 4:15
Coen Klosters20-Jun-12 4:15 
QuestionVery good job ! How about VB version of the code? Pin
Member 85955968-Mar-12 23:44
Member 85955968-Mar-12 23:44 
GeneralGreat Solution! Pin
raananv25-Oct-11 23:13
raananv25-Oct-11 23:13 
GeneralMy vote of 4... Pin
Jun Du14-Mar-11 5:05
Jun Du14-Mar-11 5:05 
GeneralRe: My vote of 4... Pin
Lars Pehrsson14-Mar-11 22:27
Lars Pehrsson14-Mar-11 22:27 
GeneralSource code is unavailable Pin
robertjb207-Mar-11 4:27
professionalrobertjb207-Mar-11 4:27 
GeneralRe: Source code is unavailable Pin
Lars Pehrsson7-Mar-11 6:04
Lars Pehrsson7-Mar-11 6:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.