- An Introduction to .NET Compact Framework Graphics
- Drawing on the Display Screen
- Raster Graphics
- Vector Graphics
- CONCLUSION
Drawing on the Display Screen
The various System.Drawing classes in the .NET Compact Framework exist for two reasons. The first and most important reason is for output to the display screen. The second reason, which exists to support the first reason, is to enable drawing to bitmaps, which can later be displayed on the display screen.
Taken together, the various classes in the System.Drawing namespace support all three families of graphical output: text, raster, and vector. You can draw text onto the display screen using a variety of sizes and styles of fonts. You can draw with raster functions, including functions that draw icons, functions that draw bitmaps, and functions that fill regions10 or the entire display screen. The third family of graphical functions, vector functions, supports the drawing of lines, polygons, rectangles, and ellipses on the display screen.
Accessing a Graphics Object
For a .NET Compact Framework program to draw on the display screen, it must have an instance of the Graphics classmeaning, of course, a Graphics object. A quick visit to the online documentation in the MSDN Library shows two interesting things about the Graphics class. First, this class provides no public constructors. Second, this class cannot be inherited by other classes. Thus you might wonder how to access a Graphics object.
Close study of the .NET Compact Framework classes reveals that there are three ways to access a Graphics object. Two are for drawing on a display screen, and one is for drawing on a bitmap. Table 15.5 summarizes three methods that are needed to gain access to a Graphics object. We include a fourth method in the table, Dispose, because you need to call that method to properly dispose of a Graphics object in some circumstances.
Table 15.5. .NET Compact Framework Methods for Accessing a Graphics Object
Namespace |
Class |
Method |
Comment |
---|---|---|---|
System.Drawing |
Graphics |
FromImage |
Creates a Graphics object for drawing onto a bitmap. When done drawing, clean up the Graphics object by calling the Dispose method. |
Graphics |
Dispose |
Reclaims memory used by Graphics objects. |
|
System.Windows.Forms |
Control |
CreateGraphics |
Creates a Graphics object for drawing in the client area of a control. As indicated in Table 15.6, only three control classes support this method. When done drawing, clean up the Graphics object by calling the Dispose method. |
Control |
Paint event handler |
Obtains a Graphics object to handle a Paint event. As indicated in Table 15.6, only five control classes support this event. Do not call the Dispose method when done drawing. |
The display screen is a shared resource. A multitasking, multithreaded operating system like Windows CE needs to share the display screen and avoid conflicts between programs. For that reason, Windows CE uses the same mechanism used by Windows on the desktop: Drawing on a display screen is allowed only in a window (i.e., in a form or a control).
To draw on the display screen, a program draws in a control. You get access to a Graphics object for the display screen, then, through controls. Not just any control class can provide this access, howeveronly the control classes that derive from the Control class can.
One way to get a Graphics object for the display screen involves the Paint event. The Paint event plays a very important role in the design of the Windows CE user interface, a topic we discuss later in this chapter. Access to a Graphics object is provided to a Paint event handler method as a property of its PaintEventArgs parameter. Incidentally, when you get a Paint event, you are allowed to use the Graphics object while responding to the event. You are not allowed to hold onto a reference to the Graphics object because the .NET Compact Framework needs to recycle the contents of that Graphics object for other controls to use.11
A second way to get a Graphics object is by calling the CreateGraphics method, a method defined in the Control class (and therefore available to classes derived from the Control class). Using the Graphics object returned by this call, your program can draw inside a control's client area. Although the method name suggests that it is creating a Graphics object, this is not what happens. Instead, like the Graphics object that arrives with the Paint event, the Graphics object that is provided by the CreateGraphics method is loaned to you from a supply created and owned by the Windows CE window manager. Therefore, you are required to return this object when you are done by calling the Graphics object's Dispose method. Failure to make this call results in a program hanging.
Calling the Dispose Method for a Graphics Object
There are two ways to get a Graphics object, but you need to call the Dispose method for only one of those ways. You must call the Dispose method for Graphics objects that are returned by the CreateGraphics method. But you do not call Dispose for Graphics objects that are provided as a parameter to the Paint event handler.
The third way to get a Graphics object is by calling the static FromImage method in the Graphics class. On the desktop, the Image class is an abstract class that serves as the base class for the Bitmap and Metafile classes. Because metafiles are not supported in the .NET Compact Framework, the FromImage method can return only a Graphics object for a bitmap. You can use the resulting Graphics object to draw onto a bitmap in the same way that the Graphics object described earlier is used to draw on a display screen. We are going to discuss drawing to bitmaps later in this chapter; for now, we explore the subject of drawing in controls.
As we discussed in Chapter 7, a main theme for .NET Compact Framework controls is that "inherited does not mean supported." Of the 28 available .NET Compact Framework control classes, only 5 support drawing. To help understand what types of drawing are supported, we start by identifying the specific controls that you can draw onto. We then cover the most important control event for drawing, the Paint event. We then discuss how nonPaint event drawing differs from Paint event handling.
Drawing in Controls
In the desktop .NET Framework, a program can draw onto any type of control (including onto forms). This feature is sometimes referred to as owner-draw support, a feature first seen in native-code programming for just a few of the Win32 API controls. The implementers of the .NET Framework for the desktop seem to think that this feature is something that every control should support. On the desktop, every control supports the owner-draw feature. In other words, you can get a Graphics object for every type of control12 and use that object to draw inside the client area of any control. Owner-draw support is widely available because it allows programmers to inherit from existing control classes and change the behavior and appearance of those classes. This support allows the creation of custom control classes from existing control classes.
Things are different in the .NET Compact Framework, for reasons that are directly attributable to the .NET Compact Framework design goals. As we discussed in detail in Chapter 7, the .NET Compact Framework itself was built to be as small as possible and also to allow .NET Compact Framework programs to run with reasonable performance. The result is a set of controls with the following qualities:
-
.NET Compact Framework controls rely heavily on the built-in, Win32 API control classes.
-
.NET Compact Framework controls do not support every PME inherited from the base Control13 class.
The result is that only a few .NET Compact Framework controls provide owner-draw support. In particular, five control classes support the Paint event. Only three control classes support the CreateGraphics method. Table 15.6 summarizes the support for drawing in .NET Compact Framework control classes.
As suggested by the column headings in Table 15.6, there are two types of drawing: Paint event drawing and CreateGraphics method drawing. The clearest way to describe the difference is relative to events because of the unique role played by the Paint event and its associated Paint event handler method. From this perspective, the two types of drawing are better stated as Paint event drawing and drawing for other events. All five controls in Table 15.6 support Paint event drawing. We turn our attention now to the subject of the Paint event and its role in the Windows CE user interface.
Table 15.6. Support for Drawing in .NET Compact Framework Control Classes
Class |
Paint Event |
CreateGraphics Method |
---|---|---|
Control |
Yes |
Yes |
DataGrid |
Yes |
Yes |
Form |
Yes |
Yes |
Panel |
Yes |
No |
PictureBox |
Yes |
No |
Anywhere, Anytime Control Drawing
An early definition of .NET talked about "anywhere, anytime access to information." Arbitrary boundaries are annoying. It is odd, then, that you cannot draw onto your controls anywhere at any time. But waitmaybe you can?
If you are willing to step outside of the managed-code box, you can draw on any control at any time. The .NET Compact Framework team did a great job of giving us a small-footprint set of libraries with very good performance. That is why owner-draw support is so limitednot because of any belief on the part of the .NET Compact Framework team that you should not be allowed to draw inside controls.
Native-code drawing means using GDI calls, each of which requires you to have a handle to a device context (hdc). There are two types of device contexts: those used to draw inside windows and those that can draw anywhere on a display screen. To draw in a window, you first must get the window handle (set focus to a control and then call the native GetFocus function). Call the native GetDC function to retrieve a device context handle, and call the ReleaseDC function when you are done.
A second method for accessing a device context is by using this call: hdc = CreateDC(NULL, NULL, NULL, NULL). The device context that is returned provides access to the entire display screen, not just inside windows. Among its other uses, this type of device context is useful for taking screenshots of the display screen, which can be useful for creating documentation. When done with the device context, be sure to clean up after yourself by calling the DeleteDC function.
The hdc returned by either of these functionsGetDC or CreateDCcan be used as a parameter to any GDI drawing function. When done drawing, be sure to provide your own manual garbage collection. In other words, be sure to call the ReleaseDC or DeleteDC functions.
The Paint Event
To draw in a windowthat is, in a form or in a controlyou handle the Paint14 event. This event is sent by the system to notify a window that the contents of the window need to be redrawn. In the parlance of Windows programmers, a window needs to be redrawn when some portion of its client area becomes invalid. To fix an invalid window, a control draws everything that it thinks ought to be displayed in the window.
Generating a Paint Event
The purpose of the Paint event is to centralize all the drawing for a window in one place. Before we look at more of the details of how to handle the Paint event, we need to discuss the circumstances under which a Paint event gets generated. A Paint event gets generated when the contents of a window become invalid. (We use the term window to mean a form or any control derived from the Control class.) But what causes a window to become invalid? There are several causes.
When a window is first created, its contents are invalid. When a form first appears, every control on the form is invalid. A Paint event is delivered to each control (which, in some cases, is handled by the native-code control that sits behind the managed-code control).
A window can also become invalid when it gets hidden. Actually, a hidden window is not invalid; it is just hidden. But when it gets uncovered, the window also becomes invalid. At that moment, a Paint event is generated by the system so that the window can repair itself.
A window can also become invalid when it gets scrolled. Every scroll operation causes three possible changes to the contents of a window. Some portion of the contents might disappear, which occurs when something scrolls off the screen. Nothing is required for that portion. Another portion might move because it has been scrolled up (or down, left, or right). Here again, nothing is required. The system moves that portion to the correct location. The third portion is the new content that is now visible to be viewed. This third portion must be drawn in response to a Paint event.
Finally, a Paint event is triggered when something in the logic of your program recognizes that the graphical display of a window does not match the program's internal state. Perhaps a new file was opened, or the user picked an option to zoom in (or out) of a view. Maybe the network went down (or came up), or the search for something ended.
To generate a Paint event for any window, a program calls one of the various versions of the Invalidate method for any Control-derived class. This method lets you request a Paint event for a portion of a window or for the entire window and optionally allows you to request that the background be erased prior to the Paint event.
This approach to graphical window drawing is not new to the .NET Compact Framework or even to the .NET environment. All GUI systems have a Paint eventfrom the first Apple Macintosh and the earliest versions of desktop Windows up to the current GUI systems shipping today. A window holds some data and displays a view of that data.
In one sense, drawing is simple: A window draws on itself using the data that it holds. And what happens if the data changes? In that case, a window must declare its contents to be invalid, which causes a Paint event to be generated. A control requests a Paint event by calling the Invalidate method. Two basic problems can be observed with the Paint event:
-
Failing to request Paint events (which causes cold windows with stale contents)
-
Requesting Paint events too often (which causes hot window flickers that annoy users)
These are different problems, but both involve calling the Invalidate method the wrong number of times. The first problem arises from not invalidating a window enough. The second problem arises from invalidating the window too often. A happy medium is needed: invalidating a window the right number of times and at just the right times.
To draw in response to a Paint event, a program adds a Paint event handler to a control. You can add a Paint event handler to any Control-derived class. But the handler is only going to get called for the five control classes listed in Table 15.6. This is just another example of the "inherited does not mean supported" behavior of .NET Compact Framework controls.
Here is an empty Paint event handler.
private void FormMain_Paint( object sender, PaintEventArgs e) { Graphics g = e.Graphics; // draw }
The second parameter to the Paint event handler is an instance of PaintEventArgs.15 A property of this class is a Graphics object, which provides the connection that we need to draw in the form. There is more to be said about the Graphics object, but first let us look at the case of drawing for events besides the Paint event.
NonPaint Event Drawing
A window that contains any graphical output must handle the Paint event. Often, the only drawing that a window requires is the drawing for the Paint event. This is especially true if the contents of the window are somewhat static. For example, Label controls are often used to display text that does not change. For a Label control, drawing for the Paint event is all that is required. However, windows whose contents must change quickly might need to draw in response to events other than the Paint event. A program that displays some type of animation, for example, might draw in response to a Timer event. A program that echoes user input might draw in response to keyboard or mouse events.
Figure 15.1 shows the DrawRectangles program, a sample program we presented in Chapter 6. This program draws rectangles in the program's main form, using a pair of (x,y) coordinates. One coordinate pair is collected for the MouseDown event, and a second coordinate pair is collected for the MouseUp event. As the user moves the mouse (or a stylus on a Pocket PC), the program draws a stretchable rubber rectangle as the mouse/stylus is moved from the MouseDown point to the MouseUp point. The program accumulates rectangles as the user draws them.
Figure 15.1 A stretchable rubber rectangle created in the DrawRectangles program
The DrawRectangles program uses both Paint and nonPaint event drawing. In response to the Paint event, the program draws each of the accumulated rectangles. In response to the MouseMove event, the stretchable rectangle is drawn to allow the user to preview the result before committing to a specific location.
The basic template for the code used in nonPaint event drawing appears here.
Graphics g = CreateGraphics(); // Draw g.Dispose();
This follows a programming pattern familiar to some as the Windows sandwich.16 The top and bottom lines of code make up the two pieces of breadthese are always the same. The filling in between the two slices of bread consists of the drawing, which is accomplished with the drawing methods from the Graphics class.