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:
this.scalablePictureBox.Picture = image;
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.
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.
private void OnResize(object sender, System.EventArgs e)
{
ScalePictureBoxToFit();
RefreshContextMenuStrip();
}
private void ScalePictureBoxToFit()
{
if (this.Picture == null)
{
...
}
else if (this.pictureBoxSizeMode == PictureBoxSizeMode.Zoom ||
(this.Picture.Width <= this.ClientSize.Width &&
this.Picture.Height <= this.ClientSize.Height))
{
...
}
else
{
...
this.AutoScroll = true;
}
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.
private void RefreshContextMenuStrip()
{
int minScalePercent = GetMinScalePercent();
if (minScalePercent == MAX_SCALE_PERCENT)
{
this.ContextMenuStrip = null;
}
else
{
this.pictureBoxContextMenuStrip.SuspendLayout();
this.pictureBoxContextMenuStrip.Items.Clear();
...
...
for (int scale = minScalePercent / 10 * 10 + 10;
scale <= MAX_SCALE_PERCENT; scale += 10)
{
...
}
this.pictureBoxContextMenuStrip.ResumeLayout();
this.ContextMenuStrip = this.pictureBoxContextMenuStrip;
CheckLastSelectedMenuItem();
}
SetCursor4PictureBox();
}
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:
[DllImport("user32.dll",
EntryPoint = "LoadCursorFromFileW",
CharSet = CharSet.Unicode)]
static public extern IntPtr LoadCursorFromFile(string fileName);
public static Cursor CreateCursorFromFile(String cursorResourceName)
{
Stream inputStream = GetEmbeddedResourceStream(cursorResourceName)
byte[] buffer = new byte[inputStream.Length];
inputStream.Read(buffer, 0, buffer.Length);
inputStream.Close();
String tmpFileName = System.IO.Path.GetRandomFileName();
FileInfo tempFileInfo = new FileInfo(tmpFileName);
FileStream outputStream = tempFileInfo.Create();
outputStream.Write(buffer, 0, buffer.Length);
outputStream.Close();
IntPtr cursorHandle = LoadCursorFromFile(tmpFileName);
Cursor cursor = new Cursor(cursorHandle);
tempFileInfo.Delete();
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:
private void DrawReversibleRect(Rectangle rect)
{
rect.Location = PointToScreen(rect.Location);
ControlPaint.DrawReversibleFrame(rect, Color.Red, FrameStyle.Dashed);
}
private void pictureTracker_MouseDown(object sender, MouseEventArgs e)
{
isDraggingPictureTracker = true;
draggingRectangle = this.pictureTracker.Bounds;
DrawReversibleRect(draggingRectangle);
}
private void pictureTracker_MouseMove(object sender, MouseEventArgs e)
{
if (isDraggingPictureTracker)
{
if (this.ClientRectangle.Contains(newPictureTrackerArea))
{
DrawReversibleRect(draggingRectangle);
draggingRectangle = newPictureTrackerArea;
DrawReversibleRect(draggingRectangle);
}
}
}
private void pictureTracker_MouseUp(object sender, MouseEventArgs e)
{
if (isDraggingPictureTracker)
{
isDraggingPictureTracker = false;
DrawReversibleRect(draggingRectangle);
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
.