65.9K
CodeProject 正在变化。 阅读更多。
Home

一个可滚动、可缩放、可调整大小的图片框

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (78投票s)

2006年8月28日

4分钟阅读

viewsIcon

359355

downloadIcon

27835

一个带上下文菜单的可滚动、可缩放、可调整大小的图片框。

Sample Image

引言

ScalablePictureBox 具有以下功能。

  • 可滚动:通过滚动条、鼠标滚轮和图片跟踪器滚动图片。
  • 可缩放:通过上下文菜单和放大/缩小光标来放大/缩小图片。
  • 可调整大小:调整图片框的大小,并根据要显示的图片大小自动创建缩放上下文菜单。

ScalablePictureBox 使用 PictureBoxPictureBoxSizeMode.Zoom 属性,因此只能在 .NET Framework 2.0 上运行。但是,通过在 Paint 事件中绘制图片,可以轻松地将其修改为在 .NET Framework 1.1 环境中使用。

背景

图像显示应用程序需要在有限的窗体区域内显示不同大小的图片。要显示的图片大小通常很大,例如,300 兆像素的数码相机照片大小约为 2000*1500 像素。用户希望看到整个图像,或者适当缩小的图像。我在开发免费相册管理应用程序(QAlbum.NET)时,需要一个可滚动的图片框控件,于是我开发了 ScalablePictureBox

使用控件

使用 ScalablePictureBox 非常简单。您可以像此处所示一样使用 ScalablePictureBox

// 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;

结构

ScalablePictureBox 控件由四个用户控件组成。结构如图 1 所示。它使用了外观(Facade)和中介者(Mediator)设计模式。ScalablePictureBoxScalablePictureBox 控件的外观。它还充当中介者,管理 ScalablePictureBoxImpPictureTrackerScalablePictureBoxImp 是可滚动、可缩放、可调整大小的图片框的核心实现。PictureTracker 是一个跟踪器、一个滚动器以及当前图片的缩略图查看器。TransparentButton 是一个微小的用户控件,用作 PictureTracker 中的关闭按钮。Util 提供了控件的一些辅助函数。

Structure of ScalablePictureBox

工作原理

ScalablePictureBox 是一个外观类。应用程序应使用 ScalablePictureBox 来显示图片,而不是直接使用 ScalablePictureBoxImp 控件。因此,ScalablePictureBoxImp 具有 internal 访问权限。另一方面,ScalablePictureBox 控件会管理和协调 ScalablePictureBoxImpPictureTracker;例如,当当前图片放大时显示 PictureTracker,当当前图片完全显示时隐藏 PictureTracker

ScalablePictureBoxImp 使用具有 PictureBoxSizeMode.Zoom 属性的 PictureBox 来显示图像。因此,图像将以缩放模式显示。ScalablePictureBox 动态更改 PictureBox 的大小以适应所选的缩放率,并在 PictureBox 的大小大于客户端区域大小时将 AutoScroll 属性设置为 true,从而使图像可滚动。

/// <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();

}

缩放上下文菜单应在加载图像时或客户端大小更改时重新创建。如果当前图像为 null 或当前图像的大小太小无法放大,则应将 ContextMenuStrip 属性设置为 null。我们应该记住选定的缩放率,以便在用户下次弹出上下文菜单时,当用户选择了缩放菜单项时,用户知道当前的缩放率。

/// <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 则使用放大光标;如果当前图像以滚动模式显示,则使用缩小光标。当鼠标左键单击图片框时,如果当前模式为滚动模式,则当前图片将以适合宽度模式显示;如果当前模式为适合宽度模式,则当前图片将以全尺寸模式显示。

ScalablePictureBox 的缩放光标是彩色光标。我们使用 user32.dllLoadCursorFromFileW 函数来加载彩色光标,并使用以下实用方法

/// <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 出于性能考虑创建当前图片的缩略图。当滚动当前图片时,它会调整缩略图的突出显示区域,因此用户可以知道当前图片的哪个部分正在显示。用户还可以通过拖动突出显示区域来滚动当前图片。默认情况下,PictureTracker 位于 ScalablePictureBox 控件的右下角。问题是右下角的图片区域被 PictureTracker 控件隐藏了,用户看不到图片的那一部分。为了解决这个问题,ScalablePictureBox 提供了一个功能,可以将 PictureTracker 控件移动到 ScalablePictureBox 控件内的任何位置。用户可以将 PictureTracker 移动到其他位置以显示图片的隐藏部分。ScalablePictureBox 使用橡皮筋绘图技术来移动 PictureTracker 控件,模拟 XOR 绘图,因为 .NET Framework 不提供 XOR 绘图方法。以下代码显示了橡皮筋绘图技术。

/// <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;
    }
}

改进

ScalablePictureBox 控件的最大放大率为 100%。某些应用程序可能需要更高的放大率来显示更详细的图片。但是,我认为我的 QAlbum.NET 不需要这种功能。因此,当前的 ScalablePictureBox 不提供此功能。

历史

  • 2006/09/28 -- 添加了一个内部图片跟踪器控件,用于跟踪图片可见部分、滚动图片以及查看当前图片的缩略图。已完全更新本文。
  • 2006/08/15 -- 文章和 ScalablePictureBox 的初始版本。
© . All rights reserved.