Click here to Skip to main content
15,880,651 members
Articles / Multimedia / GDI+
Article

A scrollable, zoomable, and scalable picture box

Rate me:
Please Sign up or sign in to vote.
4.78/5 (83 votes)
13 Nov 20064 min read 344.7K   27.7K   242   80
A scrollable, zoomable, and scalable picture box with context menu.

Sample Image

Introduction

ScalablePictureBox has the following features.

  • Scrollable: scrolling a picture with scroll bars, mouse wheel, and picture tracker.
  • Zoomable: zooming in/out a picture with context menu and zoom in/out cursors.
  • Scalable: adjusting the size of the picture box, and creating a zoom context menu automatically according to the size of the picture to be shown.

ScalablePictureBox uses the PictureBoxSizeMode.Zoom property of the PictureBox, therefore, it could only work on .NET Framework 2.0. However, it can be modified easily for use in the .NET Framework 1.1 environment by drawing a picture on the Paint event.

Background

Applications for image displaying need to show different sizes of pictures in a limited form area. The size of pictures to be shown is large usually, for example, the size of a digital camera photo with 300 Meg pixels will be about 2000*1500 pixels. Users would want to see the whole image, or a properly zoomed out image. When I was developing a free album manager application (QAlbum.NET), a scrollable picture box control was needed, and I developed ScalablePictureBox.

Using the control

Using ScalablePictureBox is very simple. You can use the ScalablePictureBox as shown here:

C#
// set an image to show
this.scalablePictureBox.Picture = image;

// set pictureBox control of scalablePictureBox
// as active control of the form
// for ScalablePictureBox to receive mouse events
this.ActiveControl = this.scalablePictureBox.PictureBox;

Structure

The ScalablePictureBox control consists of four user controls. The structure is shown in Fig. 1. It uses the Facade and Mediator design patterns. ScalablePictureBox is a facade of the ScalablePictureBox control. It also mediates ScalablePictureBoxImp and PictureTracker. ScalablePictureBoxImp is the core implementation of the scrollable, zoomable, and scalable picture box. PictureTracker is a tracker, a scroller, and a thumbnail viewer of the current picture. TransparentButton is a tiny user control used as a close button in PictureTracker. Util provides some helper functions for the controls.

Structure of ScalablePictureBox

How it works

ScalablePictureBox is a facade class. An application should use ScalablePictureBox for showing a picture instead of directly using a ScalablePictureBoxImp control. Therefore, ScalablePictureBoxImp has internal accessibility. On the other hand, ScalablePictureBox controls and mediates ScalablePictureBoxImp and PictureTracker; for example, it shows PictureTracker when the current picture is zoomed in, and hides PictureTracker when the current picture is shown fully.

ScalablePictureBoxImp displays an image by using a PictureBox with the PictureBoxSizeMode.Zoom property. So, the image would be displayed with a zooming mode. ScalablePictureBox dynamically changes the size of the PictureBox with the selected zooming rate, and sets the AutoScroll property to true if the size of the PictureBox is bigger than the size of the client area, letting the image be scrollable.

C#
/// <summary>
/// Resize picture box on resize event
/// </summary>
private void OnResize(object sender, System.EventArgs e)
{
    ScalePictureBoxToFit();
    RefreshContextMenuStrip();
}

/// <summary>
/// Scale picture box to fit to current control size and image size
/// </summary>
private void ScalePictureBoxToFit()
{
    if (this.Picture == null)
    {
        // set size of picture box the same as client size
        ...
    }
    else if (this.pictureBoxSizeMode == PictureBoxSizeMode.Zoom ||
            (this.Picture.Width <= this.ClientSize.Width && 
             this.Picture.Height <= this.ClientSize.Height))
    {
        // set size of picture box as not bigger
        // than client size according to current image
        ...
    }
    else
    {
        // set size of picture box according
        // to current scale percent selected
        ...
        this.AutoScroll = true; // let the control scrollable
    }

    // set cursor for picture box
    SetCursor4PictureBox();

    this.pictureBox.Invalidate();

}

The zooming context menu should be recreated when an image is loaded, or when the client size changes. And the ContextMenuStrip property should be set to null if the current image is null or the size of the current image is too small to zoom in. We should remember the selected zoom rate so as to let users know the current zooming rate when he/she pops up the context menu the next time, when a user has selected a zooming menu item.

C#
/// <summary>
/// Refresh context menu strip according to current image
/// </summary>
private void RefreshContextMenuStrip()
{
    int minScalePercent = GetMinScalePercent();
    if (minScalePercent == MAX_SCALE_PERCENT)
    {
        // no need popup context menu
        this.ContextMenuStrip = null;
    }
    else
    {
        this.pictureBoxContextMenuStrip.SuspendLayout();
        this.pictureBoxContextMenuStrip.Items.Clear();

        // add show whole menu item
        ...
        // add scale to fit width menu item
        ...
        // add other scale menu items
        for (int scale = minScalePercent / 10 * 10 + 10; 
             scale <= MAX_SCALE_PERCENT; scale += 10)
        {
          ...
        }

        this.pictureBoxContextMenuStrip.ResumeLayout();
        this.ContextMenuStrip = this.pictureBoxContextMenuStrip;

        // set last selected menu item checked
        CheckLastSelectedMenuItem();
    }

    SetCursor4PictureBox();  // update cursor
}

ScalablePictureBox uses a zoom in cursor if the current image is displayed in fit-width mode and the image is zoomable, or uses a zoom out cursor if the current image is displayed with scrolling mode. When the picture box is left-mouse-button-clicked, the current picture would be displayed in fit-width mode if the current mode is in scrolling mode, or the current picture would be displayed in full-size mode if the current mode is in fit-width mode.

Zoom cursors of ScalablePictureBox are colored cursors. We use the LoadCursorFromFileW function of user32.dll to load colored cursors, with the following utility method:

C#
/// <summary>
/// Load colored cursor handle from a file
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
[DllImport("user32.dll", 
  EntryPoint = "LoadCursorFromFileW",
  CharSet = CharSet.Unicode)]
static public extern IntPtr LoadCursorFromFile(string fileName);

/// <summary>
/// Create cursor from embedded cursor
/// </summary>
/// <param name="cursorResourceName">embedded cursor resource name</param>
/// <returns>cursor</returns>
public static Cursor CreateCursorFromFile(String cursorResourceName)
{
    // read cursor resource binary data
    Stream inputStream = GetEmbeddedResourceStream(cursorResourceName)
    byte[] buffer = new byte[inputStream.Length];
    inputStream.Read(buffer, 0, buffer.Length);
    inputStream.Close();

    // create temporary cursor file
    String tmpFileName = System.IO.Path.GetRandomFileName();
    FileInfo tempFileInfo = new FileInfo(tmpFileName);
    FileStream outputStream = tempFileInfo.Create();
    outputStream.Write(buffer, 0, buffer.Length);
    outputStream.Close();

    // create cursor
    IntPtr cursorHandle = LoadCursorFromFile(tmpFileName);
    Cursor cursor = new Cursor(cursorHandle);
    tempFileInfo.Delete();  // delete temporary cursor file

    return cursor;
}

PictureTracker creates a thumbnail of the current picture for performance consideration. When the current picture is scrolled, it adjusts the highlighted area of the thumbnail, therefore a user could know which part of the current picture is shown. And, a user can scroll the current picture by dragging the highlighted area with the mouse. PictureTracker is located at the bottom right corner in the ScalablePictureBox control, by default. The problem is that the picture area of the bottom right corner is hidden by the PictureTracker control, and a user can not see that part of the picture. For solving this problem, the ScalablePictureBox provides a functionality for moving PictureTracker control to any position within the ScalablePictureBox control. A user could move PictureTracker to other positions to show hidden parts of the picture. ScalablePictureBox uses the rubber-band drawing technique to move the PictureTracker control, simulating XOR drawing, because the .NET Framework doesn't provide the XOR drawing method. The following code shows the rubber-band drawing technique:

C#
/// <summary>
/// Draw a reversible rectangle
/// </summary>
/// <param name="rect">rectangle to be drawn</param>
private void DrawReversibleRect(Rectangle rect)
{
    // Convert the location of rectangle to screen coordinates.
    rect.Location = PointToScreen(rect.Location);

    // Draw the reversible frame.
    ControlPaint.DrawReversibleFrame(rect, Color.Red, FrameStyle.Dashed);
}

/// begin to drag picture tracker control
private void pictureTracker_MouseDown(object sender, MouseEventArgs e)
{
     // Make a note that we are dragging picture tracker control
    isDraggingPictureTracker = true;

    // Store the last mouse poit for this rubber-band rectangle.

    // draw initial dragging rectangle
    draggingRectangle = this.pictureTracker.Bounds;
    DrawReversibleRect(draggingRectangle);
}

/// dragging picture tracker control in mouse dragging mode
private void pictureTracker_MouseMove(object sender, MouseEventArgs e)
{
    if (isDraggingPictureTracker)
    {
        // caculating next candidate dragging rectangle
        // saving current mouse position to be used for next dragging

        // dragging picture tracker only when the candidate dragging rectangle
        // is within this ScalablePictureBox control
        if (this.ClientRectangle.Contains(newPictureTrackerArea))
        {
            // removing previous rubber-band frame
            DrawReversibleRect(draggingRectangle);
            // updating dragging rectangle
            draggingRectangle = newPictureTrackerArea;
            // drawing new rubber-band frame
            DrawReversibleRect(draggingRectangle);
        }
    }
 }

/// end dragging picture tracker control
private void pictureTracker_MouseUp(object sender, MouseEventArgs e)
{
    if (isDraggingPictureTracker)
    {
        isDraggingPictureTracker = false;

        // erase dragging rectangle
        DrawReversibleRect(draggingRectangle);

        // move the picture tracker control to the new position
        this.pictureTracker.Location = draggingRectangle.Location;
    }
}

Improvements

The maximum zoom-in rate of the ScalablePictureBox control is 100%. Some applications may need bigger zoom-in rate to show a more detailed picture. However, I don't think my QAlbum.NET needs this kind of a feature. Therefore, the current ScalablePictureBox doesn't provide this feature.

History

  • 28/09/2006 -- Added an internal picture tracker control for tracking the visible part of a picture, scrolling a picture, and viewing the thumbnail of the current picture. Updated this article completely.
  • 15/08/2006 -- Initial version of the article and ScalablePictureBox.

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


Written By
Web Developer
China China
Working as a senior software engineer after having worked in a university for about 10 years.

Comments and Discussions

 
GeneralExcellent work Pin
frenchwiz28-Aug-07 11:43
frenchwiz28-Aug-07 11:43 
GeneralRotate with Multi-page Tiff Pin
ChameleonSolutions21-Aug-07 5:08
ChameleonSolutions21-Aug-07 5:08 
GeneralWorks Great! Pin
merlin9811-Aug-07 7:37
professionalmerlin9811-Aug-07 7:37 
QuestionA Paint event is not triggered Pin
asavol25-Jun-07 0:56
asavol25-Jun-07 0:56 
AnswerRe: A Paint event is not triggered Pin
asavol28-Jun-07 20:27
asavol28-Jun-07 20:27 
GeneralLittle speed improvement Pin
mj7721-May-07 2:52
mj7721-May-07 2:52 
Generalvc2003- Picture box and mouse wheel event Pin
minad_7868-May-07 22:42
minad_7868-May-07 22:42 
QuestionA few questions re: mouseclicks Pin
mikekreuzer6-May-07 17:48
mikekreuzer6-May-07 17:48 
GeneralRe: A few questions re: mouseclicks Pin
mikekreuzer7-May-07 22:18
mikekreuzer7-May-07 22:18 
Questionhelp Pin
xenia gr29-Mar-07 23:53
xenia gr29-Mar-07 23:53 
AnswerRe: help Pin
Code_Newbie26-Oct-11 11:13
Code_Newbie26-Oct-11 11:13 
QuestionAny way to use this in VC6? Pin
Chhoto Bou8-Jan-07 18:26
Chhoto Bou8-Jan-07 18:26 
AnswerRe: Any way to use this in VC6? Pin
Bingzhe Quan9-Jan-07 1:48
Bingzhe Quan9-Jan-07 1:48 
QuestionZoom more than %100 Pin
dadaruh7-Jan-07 4:02
dadaruh7-Jan-07 4:02 
AnswerRe: Zoom more than %100 Pin
Bingzhe Quan7-Jan-07 15:05
Bingzhe Quan7-Jan-07 15:05 
GeneralRe: Zoom more than %100 Pin
zevz30-Jul-07 4:22
zevz30-Jul-07 4:22 
GeneralRe: Zoom more than %100 Pin
liycncn30-Jan-08 2:15
liycncn30-Jan-08 2:15 
AnswerRe: Zoom more than %100 Pin
Yang Li11-Dec-12 5:29
Yang Li11-Dec-12 5:29 
GeneralUse with StatusBar - Hides Scrollbar [modified] Pin
Mark Johnson15-Nov-06 18:22
Mark Johnson15-Nov-06 18:22 
GeneralRe: Use with StatusBar - Hides Scrollbar Pin
Bingzhe Quan20-Nov-06 15:11
Bingzhe Quan20-Nov-06 15:11 
GeneralRe: Use with StatusBar - Hides Scrollbar Pin
Mark Johnson21-Nov-06 20:08
Mark Johnson21-Nov-06 20:08 
GeneralCongrats on the new version Pin
Marcos Meli13-Nov-06 10:34
Marcos Meli13-Nov-06 10:34 
GeneralSource Link not Working .... Pin
dotnet676-Nov-06 4:34
dotnet676-Nov-06 4:34 
GeneralRe: Source Link not Working .... Pin
Bingzhe Quan6-Nov-06 15:58
Bingzhe Quan6-Nov-06 15:58 
GeneralJust what I needed. Thanks!!! Pin
M.Lansdaal3-Nov-06 9:58
M.Lansdaal3-Nov-06 9:58 

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.