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 regions
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.
1. Accessing a Graphics Object
For a .NET Compact Framework program to draw on the display screen, it must have an instance of the Graphics class—meaning, 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 three ways to access a Graphics object: two for a display screen and one for bitmaps. Table 1 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 1. .NET Compact Framework Methods for Accessing a Graphics Object
Namespace | Class | Method | Comment |
---|
System.Drawing | Graphics | FromImage | Creates a Graphics object for drawing a bitmap. Clean up by calling Dispose |
System.Drawing | Graphics | Dispose | Reclaims a Graphics object’s memory |
System.Windows.Forms | Control | CreateGraphics | Creates a Graphics object for drawing in a control’s client area. Clean up by calling Dispose |
System.Windows.Forms | Control | Paint event handler | Obtains a Graphics object far a Paint event. Do not call the Dispose method |
The display screen is a shared resource. A
multitasking, multithreaded operating system such as 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, however—only 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 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 on to 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.
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.
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.
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 drawing types are supported, we start by identifying the
specific controls onto which you can draw. We then cover the most
important control event for drawing, the Paint event. We then discuss how non-Paint event drawing differs from Paint event handling.
2. 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 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 control
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. 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 Property, Method, and Event (a.k.a. “PME”) inherited from the base Control class.
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. We turn our attention now to the subject of the Paint event and its role in the Windows CE user interface.
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 wait—maybe you can.
If you are willing to step outside 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 limited—not 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.
The 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 screen shots 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 functions—GetDC or CreateDC—can
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 function.
|
3. The Paint Event
To draw in a window—that is, in a form or in a control—you handle the Paint
event. The system sends this event 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.
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
event—from 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 (this causes cold windows with stale contents)
Requesting Paint events too often (this 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 often 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. 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. 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.
4. Non-Paint 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 1 shows the DrawRectangles
program, a sample vector drawing program. 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.
The DrawRectangles program uses both Paint and non-Paint 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 non-Paint event drawing appears here:
Graphics g = CreateGraphics();
// Draw
g.Dispose();
This follows a programming pattern familiar to some as the Windows sandwich.
The top and bottom lines of code make up the two pieces of bread—these
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.