Windows Forms 2.0 Programming (Microsoft .NET Development Series)

As useful as curves and lines are, most modern applications also include the need to load and display professionally produced, prepackaged images. Also, some applications themselves produce images that can be saved to a file for later display. Both kinds of applications are supported by the two kinds of images in .NET: bitmaps and metafiles.

A bitmap is a set of pixels at certain color values stored in a variety of standard raster formats such as Graphics Interchange Format (GIF) (.gif files), Joint Picture Experts Group (JPEG) (.jpg files), and Portable Network Graphics (PNG) (.png files), as well as Windows-specific formats such as Windows bitmap (.bmp files) and Windows icon (.ico files). A metafile is a set of shapes that make up a vector format, such as a GraphicsPath, but can also be loaded from Windows metafile (.wmf) and enhanced Windows metafile (.emf) formats. In general, raster formats provide more detail, whereas metafiles offer better resizing support.

Loading and Drawing Images

Bitmaps and metafiles can be loaded from files in the file system as well as files embedded as resources.[8] However, you must use the appropriate class. The Bitmap class (from the System.Drawing namespace) handles only raster formats and doesn't support alpha channels for 32 bits per pixel (BPP) ARGB images, and the Metafile class (from the System.Drawing.Imaging namespace) handles only vector formats.[9] Both the Bitmap class and the Metafile class derive from a common base class, the Image class. Image objects are what you deal with most of the time, whether you're drawing them into a Graphics object or setting a Form object's BackgroundImage property.

[8] For details of loading images from resources, see Chapter 13: Resources.

[9] Bitmap does support alpha channels for .png files, but not for .bmp files. Alpha channels aren't officially part of the bitmap specification, even though some applications (like Adobe Photoshop) do support alpha channels on bitmaps.

The easiest way to load the image is to pass a file name to the appropriate class's constructor. After an image has been loaded, it can be drawn using the Graphics.DrawImage method:

using( Metafile wmf = new Metafile(@"2DARROW3.WMF") ) { // Draw the full image, unscaled and // clipped only to the Graphics object g.DrawImage(wmf, new PointF(0, 0)); } using( Bitmap bmp = new Bitmap(@"c:\windows\soap bubbles.bmp") ) { g.DrawImage(bmp, new PointF(100, 100)); } using( Bitmap ico = new Bitmap(@"POINT10.ICO") ) { g.DrawImage(ico, new PointF(200, 200)); }

Drawing an image using a point causes the image to be rendered at its native size and clipped only by the Graphics object. You can be explicit about this desire by calling DrawImageUnscaled, but it acts no differently from passing only a point to DrawImage. If you'd like to draw your image both unscaled and clipped, you can call the Graphics.DrawImageUnscaledAndClipped method, passing an image and the target rectangle.

Scaling, Clipping, Panning, and Skewing

Drawing an image unscaled, although useful, is somewhat boring. Often, you'd like to perform operations on the image as it's drawn to achieve effects. For example, to scale an image as it's drawn to fit into a rectangle, you pass a rectangle instead of a point to DrawImage:

// Scale the image to the rectangle Rectangle rect = new Rectangle(...); g.DrawImage(bmp, rect);

Going the other way, if you'd like to clip an image but leave it unscaled, you can use the DrawImage method, which takes both a source and a destination rectangle of the same size (Figure 5.24 shows the difference):

Figure 5.24. Scaling an Image Versus Clipping an Image

// Clip the image to the destination rectangle Rectangle srcRect = new Rectangle(...); Rectangle destRect = srcRect; g.DrawImage(bmp, destRect, srcRect, g.PageUnit);

The code that does the clipping specifies a source rectangle to take from the image, and a destination rectangle on the Graphics object. Because both rectangles were the same size, there was no scaling, but this technique allows any chunk of the image to be drawn (and scaled) to any rectangle on the Graphics object. This technique also allows for panning, which offsets the upper-left corner of the image being drawn from the client rectangle of the surface the image is being drawn to (as shown in Figure 5.25):

Figure 5.25. A Form That Pans an Image in Four Directions

Bitmap bmp = new Bitmap(@"c:\windows\soap bubbles.bmp"); Size offset = new Size(0, 0); // Adjusted by buttons void panningPanel_Paint(object sender, PaintEventArgs e) { Graphics g = e.Graphics; Rectangle destRect = this.panningPanel.ClientRectangle; Rectangle srcRect = new Rectangle(this.offset.Width, this.offset.Height, destRect.Width, destRect.Height); g.DrawImage(this.bmp, destRect, srcRect, g.PageUnit); }

Not only can you scale an image (or part of an image) to a rectangle, but also you can scale an image (or part of an image) to an arbitrary parallelogram. Several of the DrawImages overloads take an array of three PointF objects that describe three points on a parallelogram, which in turn acts as the destination (the fourth point is extrapolated to make sure that it's a real parallelogram). Scaling to a nonrectangular parallelogram is called skewing because of the skewed look of the results. For example, here's a way to skew an entire image (as shown in Figure 5.26):

Figure 5.26. An Example of Skewing an Image (See Plate 13)

Bitmap bmp = new Bitmap(@"c:\windows\soap bubbles.bmp"); Size offset = new Size(0, 0); // Adjusted by buttons void skewingPanel_Paint(object sender, PaintEventArgs e) { Graphics g = e.Graphics; Rectangle rect = this.skewingPanel.ClientRectangle; Point[] points = new Point[3]; points[0] = new Point(rect.Left + this.offset.Width, rect.Top + this.offset.Height); points[1] = new Point(rect.Right, rect.Top + this.offset.Height); points[2] = new Point(rect.Left, rect.Bottom - this.offset.Height); g.DrawImage(this.bmp, points); }

Rotating and Flipping

Two other kinds of transformation that you can apply to an image are rotating and flipping. Rotating an image allows it to be turned in increments of 90 degreesthat is, 90, 180, or 270. Flipping an image allows an image to be flipped along either the X or the Y axis. You can perform these two transformations together using the values from the RotateFlipType enumeration, as shown in Figure 5.27.

Figure 5.27. The Rotating and Flipping Types from the RotateFlipType Enumeration

Notice in Figure 5.27 that both RotateNoneFlipNone and Rotate180FlipXY are the original image. All the others are either rotated or flipped, or both. To rotate only, you pick a type that includes FlipNone. To flip only, you pick a type that includes RotateNone. The values from the RotateFlipType enumeration affect an image itself using the RotateFlip method:

// Rotate 90 degrees bitmap1.RotateFlip(RotateFlipType.Rotate90FlipNone); // Flip along the X axis bitmap2.RotateFlip(RotateFlipType.RotateNoneFlipX);

The effects of rotation and flipping are cumulative. For example, rotating an image 90 degrees twice rotates it a total of 180 degrees.

Recoloring

Rotating and flipping aren't merely effects applied when drawing; rather, these operations affect the contents of the image. You can also transform the contents using an ImageAttributes object that contains information about what kind of transformations to make. For example, one of the things you can do with an ImageAttributes class is to map colors:

void mappedColorsPanel_Paint(object sender, PaintEventArgs e) { Graphics g = e.Graphics; using( Bitmap bmp = new Bitmap(this.GetType(), "INTL_NO.BMP") ) { // Set the image attribute's color mappings ColorMap[] colorMap = new ColorMap[1]; colorMap[0] = new ColorMap(); colorMap[0].OldColor = Color.Lime; colorMap[0].NewColor = Color.White; ImageAttributes attr = new ImageAttributes(); attr.SetRemapTable(colorMap); // Draw using the color map Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height); rect.Offset(...); g.DrawImage( bmp, rect, 0, 0, rect.Width, rect.Height, g.PageUnit, attr); } }

This code first creates an array with a single ColorMap object, which contains the old color to transform from and the new color to transform to. The color map array is passed to a new ImageAttribute class via the SetRemapTable. The ImageAttribute object is then passed to the DrawImage function, which does the color mapping as the image is drawn. Figure 5.28 shows an example.

Figure 5.28. An Example of Mapping Color.Lime to Color.White

Notice that in addition to mapping the colors, the sample code uses the Width and Height properties of the Bitmap class. The Bitmap class, as well as the Image base class and the Metafile class, provides a great deal of information about the image.

Another useful piece of information is the color information at each pixel. For example, instead of hard-coding lime as the color, we could use the pixel information of the bitmap itself to pick the color to replace:

ColorMap[] colorMap = new ColorMap[1]; colorMap[0] = new ColorMap(); colorMap[0].OldColor = bmp.GetPixel(0, bmp.Height - 1); colorMap[0].NewColor = Color.White;

In this case, we're mapping whatever color is at the bottom left as the pixel to replace. In addition to replacing colors, the ImageAttributes object can contain information about remapping palettes, setting gamma correction values, mapping color to grayscale, and other color-related options as well as the wrap mode (as with brushes).

Transparency

Unfortunately, simply mapping to white or any other color isn't useful if the image needs to be drawn on top of something else that you'd like to show through. For this case, a special color called Transparent allows the mapped color to disappear instead of being replaced with another color:

ColorMap[] colorMap = new ColorMap[1]; colorMap[0] = new ColorMap(); colorMap[0].OldColor = bmp.GetPixel(0, bmp.Height - 1); colorMap[0].NewColor = Color.Transparent;

Figure 5.29 shows the effects of using Color.Transparent.

Figure 5.29. Using Color.Transparent in a Color Map

Again, I used the bottom-left pixel as the color to replace, the convention used in other parts of .NET. In fact, if you're going to always draw a bitmap with a transparent color and if the color to be made transparent is in the bitmap itself in the bottom-left pixel, you can save yourself the trouble of building a color map and instead use the MakeTransparent method:

// Make the bottom-left pixel the transparent color bmp.MakeTransparent(); g.DrawImage(bmp, rect);

If the pixel you'd like to use as the transparency color isn't in the bottom left of the bitmap, you can also use the MakeTransparent overload, which takes a color as an argument. Calling MakeTransparent actually replaces the pixels of the transparency color with the Color.Transparent value. Some raster formats, such as the GIF and Windows icon formats, allow you to specify a transparency color value as one of their legal values. However, even if the raster format itself doesn't support a transparency color, all Bitmap objects, regardless of the raster format, support the MakeTransparent method.

Animation

Just as some raster formats support transparency as a native color, some also support animation. One in particular is the GIF format. Images expose support for animation by supporting more than one image in a single file. GIFs support animation by storing two or more images in an array that represents a time dimension, but other formats (such as TIFF files) can support different resolutions or multiple images as pages. You can count how many pages are in each "dimension" by calling the GetFrameCount method with FrameDimension objects exposed by properties from the FrameDimension class:

// Will throw exceptions if image format doesn't support // multiple images along requested dimension Bitmap gif = new Bitmap(typeof(AnimationForm), "animatedgif.gif"); int timeFrames = gif.GetFrameCount(FrameDimension.Time); int pageFrames = gif.GetFrameCount(FrameDimension.Page); int resolutionFrames = gif.GetFrameCount(FrameDimension.Resolution);

Selecting which frame to be displayed when the image is drawn is a matter of selecting the "active" frame along a dimension:

int frame = 4; // Needs to be between 0 and frame count -1 gif.SelectActiveFrame(FrameDimension.Time, frame); g.DrawImage(gif, this.ClientRectangle);

In addition to the multiple frames, the GIF format encodes timing information for each frame. However, that's where things get tricky. Because different image formats support different information, the Image class exposes "extra" information via its GetPropertyItem method. This method takes a numeric ID and returns a generic PropertyItem object. The IDs themselves are defined only in a GDI+ header file and the PropertyItem object's Value property. The Value property exposes the actual data as an array of bytes that must be interpreted, making usage from .NET difficult. For example, here's how to get the timings for a GIF file:

// Get bytes describing each frame's time delay int PropertyTagFrameDelay = 0x5100; // From GdiPlusImaging.h PropertyItem prop = gif.GetPropertyItem(PropertyTagFrameDelay); byte[] bytes = prop.Value; // Convert bytes into an array of time delays int frames = gif.GetFrameCount(FrameDimension.Time); int[] delays = new int[frames]; for( int frame = 0; frame != frames; ++frame ) { // Convert each 4-byte chunk into an integer delays[frame] = BitConverter.ToInt32(bytes, frame * 4); }

After you have the time delays, you can start a timer and use the SelectActiveFrame method to do the animation. If you do it that way, make sure to convert the delays to milliseconds (1/1000 second), which is what .NET timers like, from centiseconds (1/100 second), which is what GIF time delays are specified in. Or just use the ImageAnimator helper class, which can do all this for you:

// Load animated GIF Bitmap gif = new Bitmap(@"c:\animatedgif.gif"); void AnimationForm_Load(object sender, EventArgs e) { ... // Check whether image supports animation if( ImageAnimator.CanAnimate(gif) ) { // Subscribe to an event indicating the next frame should be shown ImageAnimator.Animate(gif, gif_FrameChanged); } ... } void gif_FrameChanged(object sender, EventArgs e) { if( this.InvokeRequired ) { // Transition from worker thread to UI thread this.BeginInvoke( new EventHandler(gif_FrameChanged), new object[] { sender, e }); } else { currentFrame++; this.toolStripStatusLabel1.Text = string.Format("Frame {0} of {1}", currentFrame, frameCount); if( currentFrame == frameCount ) currentFrame = 0; // Trigger Paint event to draw next frame this.Invalidate(); } } void AnimationForm_Paint(object sender, PaintEventArgs e) { // Update image's current frame ImageAnimator.UpdateFrames(gif); // Draw image's active frame Graphics g = e.Graphics; Rectangle rect = this.ClientRectangle; rect.Height -= this.statusStrip1.Height; g.DrawImage(gif, rect); }

The ImageAnimator knows how to pull the timing information out of an image and call you back when it's time to show a new frame, which is what calling ImageAnimator.Animate does. When the event is fired, invalidating the rectangle being used to draw the animated GIF triggers the Paint event. The Paint event sets the next active frame by calling ImageAnimator.UpdateFrames before drawing the active frame. Figure 5.30 shows an image being animated.

Figure 5.30. Sample Animation, Showing First, Middle, and Last Frames

The only thing that's a bit tricky is that the animated event is called back on a worker thread, not on the main UI thread, because it's not legal for the former to make any method calls on objects executing from the latter, such as forms. To avoid breaking the law, we used the BeginInvoke method to transition back from the worker thread to the UI thread to make the call. This technique is discussed in gory detail in Chapter 18: Multithreaded User Interfaces.

Many of the animation capabilities discussed so far are automatically provided by the PictureBox control, which basically wraps the appropriate ImageAnimator method calls, such as Animate. All you need to do is set the PictureBox's Image property with an image that supports animation, and it takes care of the rest:

void AnimatedPictureBoxForm_Load(object sender, EventArgs e) { Bitmap gif = new Bitmap(@"c:\animatedgif.gif"); this.animatedPictureBox.Image = gif; // Automatically begins animating }

PictureBox is perfect if all you need is to simply animate your image. Unfortunately, PictureBox doesn't provide hooks into the animation process (as the FrameChanged event does). This means that you need to code directly against ImageAnimator for such support. However, some controls, including Button, Label, and ToolStripItem, do support animation natively. Simply set the control's Image property to the animated GIF directly:

this.animatedLabel.Image = Image.FromFile(@"c:\animatedgif.gif");

Drawing to Images

Certain kinds of applications need to create images on-the-fly, often requiring that they be saved to a file. The key is to create an image with the appropriate starting parameters, which for a Bitmap means the height, width, and pixel depth. The image is then used as the "backing store" of a Graphics object. If you're interested in getting the pixel depth from the screen itself, you can use a Graphics object when creating a Bitmap:

// Get current graphics object for display Graphics displayGraphics = this.CreateGraphics(); // Create bitmap to draw into, based on existing Graphics object Image image = new Bitmap(rect.Width, rect.Height, displayGraphics);

After you have an image, you can use the Graphics.FromImage method to wrap a Graphics object around it:

// Wrap Graphics object around image to draw into Graphics imageGraphics = Graphics.FromImage(image);

After you have a Graphics object, you can draw on it as you would normally. One thing to watch out for, however, is that a Bitmap starts with all pixels set to the Transparent color. That may be exactly what you want, but if it's not, then a quick FillRectangle across the entire area of the Bitmap will set things right.

After you've done the drawing on the Graphics object that represents the image, you can draw that image to the screen or a printer, or you can save it to a file using the Save method of the Image class:

// Save created image to a file image.Save(@"c:\image.png");

Unless otherwise specified, the file is saved in PNG format, regardless of the extension on the file name.[10] If you prefer to save it in another format, you can pass an instance of the ImageFormat class as an argument to the Save method. You create an instance of the ImageFormat class using the GUID (globally unique ID) of the format, but the ImageFormat class comes with several properties prebuilt for supported formats:

[10] You might consider storing images in Portable Network Graphics (PNG) format because it is royalty-free, unlike the Graphics Interchange Format (GIF).

namespace System.Drawing.Imaging { sealed class ImageFormat { // Constructors public ImageFormat(Guid guid); // Properties public static ImageFormat Bmp { get; } public static ImageFormat Emf { get; } public static ImageFormat Exif { get; } public static ImageFormat Gif { get; } public Guid Guid { get; } public static ImageFormat Icon { get; } public static ImageFormat Jpeg { get; } public static ImageFormat MemoryBmp { get; } public static ImageFormat Png { get; } public static ImageFormat Tiff { get; } public static ImageFormat Wmf { get; } } }

As an example of creating images on-the-fly and saving them to a file, the following code builds the bitmap shown in Figure 5.31:

Figure 5.31. Example of Drawing to an Image

void saveButton_Click(object sender, EventArgs e) { Rectangle rect = new Rectangle(0, 0, 100, 100); // Get current graphics object for display using( Graphics displayGraphics = this.CreateGraphics() ) // Create bitmap to draw into based on existing Graphics object using( Image image = new Bitmap(rect.Width, rect.Height, displayGraphics) ) // Wrap Graphics object around image to draw into using( Graphics imageGraphics = Graphics.FromImage(image) ) { imageGraphics.FillRectangle(Brushes.Black, rect); imageGraphics.DrawString("Drawing to an image", ... ); // Save created image to a file image.Save(@"c:\image.png"); } }

Screen Copying

Some applications need to copy a portion of the screen to either save to file or to copy to an image-editing application for further processing. The Graphics object now implements the CopyFromScreen methods to help you do just that. The basic process is to define an area of the screen to copy from, create a copy target, copy from the screen to the target, and do something with it. The following example copies the entire screen to a Bitmap that is then scaled and displayed in a picture box control:

void captureButton_Click(object sender, EventArgs e) { // Dump screen to bitmap Rectangle screenRect = Screen.PrimaryScreen.WorkingArea; Bitmap dumpBitmap = new Bitmap(screenRect.Width, screenRect.Height); using( Graphics targetGraphics = Graphics.FromImage(dumpBitmap) ) { targetGraphics.CopyFromScreen( 0, 0, 0, 0, new Size(dumpBitmap.Width, dumpBitmap.Height)); } // Display screen dump this.screenPictureBox.BackgroundImage = dumpBitmap; this.screenPictureBox.BackgroundImageLayout = ImageLayout.Stretch; }

Although this technique allows you to capture any area of the screen, you can use a more granular approach that relies on the DrawToBitmap method, implemented by Control. The DrawToBitmap method encapsulates the work of copying any portion of a control's UI to a bitmap, including resource management. And because DrawToBitmap is implemented by Control, and because controls, container controls, user controls, and forms all derive from Control, you can enjoy the benefit of inheritance:

void captureButton_Click(object sender, EventArgs e) { // Dump form UI to a bitmap Rectangle rect = new Rectangle(0, 0, form.Size.Width, form.Size.Height); Bitmap dumpBitmap = new Bitmap(form.Size.Width, form.Size.Height); form.DrawToBitmap(dumpBitmap, rect); // Display screen dump this.screenPictureBox.BackgroundImage = dumpBitmap; this.screenPictureBox.BackgroundImageLayout = ImageLayout.Stretch; }

This example invokes DrawToBitmap on a form, capturing the entire form area and displaying it to a picture box control.

Icons

Before I wrap up the images section, I want to mention two kinds of images for which .NET provides special care: icons and cursors. You can load a Windows icon (.ico) file directly into an Icon object. The Icon class is largely a direct wrapper class around the Win32 HICON type and is provided mainly for interoperability. Unlike the Bitmap or Metafile class, the Icon class doesn't derive from the base Image class:

namespace System.Drawing { sealed class Icon : IDisposable, ... { // Constructors public Icon(Stream stream); public Icon(string fileName); public Icon(Icon original, Size size); public Icon(Stream stream, Size size); // New public Icon(string fileName, Size size); // New public Icon(Type type, string resource); public Icon(Icon original, int width, int height); public Icon(Stream stream, int width, int height); public Icon(string fileName, int width, int height); // New // Properties public IntPtr Handle { get; } public int Height { get; } public Size Size { get; } public int Width { get; } // Methods public static Icon ExtractAssociatedIcon(string filePath); // New public static Icon FromHandle(IntPtr handle); public void Save(Stream outputStream); public Bitmap ToBitmap(); } }

When setting the Icon property of a Form, for example, you use the Icon class, not the Bitmap class. Icons support construction from files and resources as well as from raw streams (if you want to create an icon from data in memory) and expose their Height and Width. For interoperability with Win32, Icons also support the Handle property and the FromHandle method. FromHandle is particularly useful if you need to convert a Bitmap to an Icon:

void ConvertBitmapToIcon() { // Get source bitmap Bitmap bitmap = new Bitmap(@"c:\windows\soap bubbles.bmp"); // Get source bitmap's icon handle IntPtr hIcon = bitmap.GetHicon(); // Convert bitmap to icon Icon icon = Icon.FromHandle(hIcon); this.Icon = icon; }

On the other hand, creating a Bitmap from an Icon is as simple as calling Icon's ToBitmap method, which copies the data to a new Bitmap object. After you've loaded an icon, you can draw it to a Graphics object using the DrawIcon or DrawIconUnstretched method:

Icon ico = new Icon("POINT10.ICO"); g.DrawIcon(ico, this.ClientRectangle); // Stretch g.DrawIconUnstretched(ico, this.ClientRectangle); // Don't stretch

As well as stand-alone .ico files, icons can be compiled into assemblies as resources. To acquire the first icon resource from an assembly (index 0), you can pull it out using Icon's ExtractAssociatedIcon method:[11]

[11] Icon contains a static overload of ExtractAssociatedIcon that accepts an additional index argument, although, for no apparent reason, it is marked as private.

Icon icon = Icon.ExtractAssociatedIcon(@"c:\windows\notepad.exe");

Several icons used by the system come prepackaged for you as properties of the SystemIcons class for your own use, as shown in Figure 5.32.

Figure 5.32. Icon Properties from the SystemIcons Class as Shown Under Windows XP

Cursors

The other Win32 graphics type that Windows Forms provides is the Cursor type. As with icons, Cursor doesn't derive from the Image base class:

namespace System.Windows.Forms { sealed class Cursor : IDisposable, ... { // Constructors public Cursor(IntPtr handle); public Cursor(Stream stream); public Cursor(string fileName); public Cursor(Type type, string resource); // Properties public static Rectangle Clip { get; set; } public static Cursor Current { get; set; } public IntPtr Handle { get; } public Point HotSpot { get; } // New public static Point Position { get; set; } public Size Size { get; } public object Tag { get; set; } // New // Methods public IntPtr CopyHandle(); public void Draw(Graphics g, Rectangle targetRect); public void DrawStretched(Graphics g, Rectangle targetRect); public static void Hide(); public static void Show(); } }

A Cursor is a graphic that represents the position of the mouse on the screen. It can take on several values based on the needs of the window currently under the cursor. For example, by default, the cursor is an arrow to indicate that it should be used to point. However, when the cursor passes over a text-editing window, it often turns into an I-beam to provide for better positioning between characters. Cursors also have a hot spot, which is the pixel in the icon image that actually cause an action. For example, the hot spot of the default arrow cursor is in the tip of the arrow, and that is why you can't click or double-click using the arrow's tail.

A cursor can be loaded from one of the system-provided cursors in the Cursors class, as shown in Figure 5.33.[12]

[12] Note that .ani files are not supported.

Figure 5.33. System Cursors from the Cursors Class

You can draw a cursor manually using the Draw or DrawStretched method of the Cursor class, but most of the time you draw a cursor by setting it as the current cursor using the static Current property of the Cursor class. Setting the current cursor remains in effect only during the current event handler and only when the cursor is over windows of that application. Changing the cursor doesn't stop another window in another application from changing it to something it finds appropriate. For example, the following code changes the application's cursor to the WaitCursor during a long operation:

void CursorsForm_Click(object sender, EventArgs e) { try { // Change the cursor to indicate that we're waiting Cursor.Current = Cursors.WaitCursor; // Do something that takes a long time... } finally { // Restore current cursor Cursor.Current = this.Cursor; } } // Cursor restored after this event handler anyway...

Notice the use of the form's Cursor property to restore the current cursor after the long operation completes. Every form and every control has a Cursor property. This cursor becomes the default when the mouse passes over the window. For example, the following code sets a form's default cursor to the Cross:

// CursorsForm.Designer.cs partial class CursorsForm { ... void InitializeComponent() { ... this.Cursor = System.Windows.Forms.Cursors.Cross; ... } }

Notice the use of InitializeComponent to set the Form's cursor, indicating that this is yet another property that can be set in the Properties window, which shows a drop-down list of all the system-provided cursors to choose from.

Using Animated and Colored Cursors

Beyond the black-and-white cursors available from the Cursors class, the cursor world is full of color and animation. Typically, colored and animated cursors are created in third-party applications and are generated as .cur and .ani files, respectively. Unfortunately, you can't create a Cursor object using either of these file types directly. Instead, you must call the LoadCursorFromFile User32 API function to load them and return a handle with which you can instantiate a Cursor. The following code shows how (for animated cursors):

// AnimatedCursorForm.cs using System.Runtime.InteropServices; ... partial class AnimatedAndColoredCursorsForm : Form { [DllImport("user32.dll")] static extern IntPtr LoadCursorFromFile(string lpFileName); static Cursor ColoredCursor; static Cursor AnimatedCursor; static AnimatedAndColoredCursorsForm() { // Load animated cursor IntPtr cursor = LoadCursorFromFile(@"c:\windows\cursors\hourglas.ani"); AnimatedCursor = new Cursor(cursor); // Load colored cursor IntPtr cursor = LoadCursorFromFile(@"c:\windows\cursors\3dgarro.cur"); ColoredCursor = new Cursor(cursor); } public AnimatedAndColoredCursorsForm() { InitializeComponent(); } void Form_MouseEnter(object sender, EventArgs e) { this.Cursor = this.ColoredCursor; } void Form_MouseLeave(object sender, EventArgs e) { this.Cursor = Cursors.Default; } void ALongRunningOperation() { this.Cursor = this.AnimatedCursor; ... this.Cursor = Cursors.Default; } }

Animated and colored cursors are a nice option when your application needs a visual flavor not afforded by the standard cursors.

Категории