Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Zooming and panning in Windows Forms with fixed focus

0.00/5 (No votes)
14 Mar 2011 1  
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:

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.

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.

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.

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.

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.

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here