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






4.78/5 (78投票s)
2006年8月28日
4分钟阅读

359355

27835
一个带上下文菜单的可滚动、可缩放、可调整大小的图片框。
引言
ScalablePictureBox
具有以下功能。
- 可滚动:通过滚动条、鼠标滚轮和图片跟踪器滚动图片。
- 可缩放:通过上下文菜单和放大/缩小光标来放大/缩小图片。
- 可调整大小:调整图片框的大小,并根据要显示的图片大小自动创建缩放上下文菜单。
ScalablePictureBox
使用 PictureBox
的 PictureBoxSizeMode.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)设计模式。ScalablePictureBox
是 ScalablePictureBox
控件的外观。它还充当中介者,管理 ScalablePictureBoxImp
和 PictureTracker
。ScalablePictureBoxImp
是可滚动、可缩放、可调整大小的图片框的核心实现。PictureTracker
是一个跟踪器、一个滚动器以及当前图片的缩略图查看器。TransparentButton
是一个微小的用户控件,用作 PictureTracker
中的关闭按钮。Util
提供了控件的一些辅助函数。
工作原理
ScalablePictureBox
是一个外观类。应用程序应使用 ScalablePictureBox
来显示图片,而不是直接使用 ScalablePictureBoxImp
控件。因此,ScalablePictureBoxImp
具有 internal
访问权限。另一方面,ScalablePictureBox
控件会管理和协调 ScalablePictureBoxImp
和 PictureTracker
;例如,当当前图片放大时显示 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.dll 的 LoadCursorFromFileW
函数来加载彩色光标,并使用以下实用方法
/// <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
的初始版本。