vendredi 4 juillet 2008

Printing and Graphics

Drawing and Printing in C#

By James Foxall,Wendy Haro-Chun

Date: Feb 15, 2002

Sample Chapter is provided courtesy of Sams.

Return to the article


C# provides an amazingly powerful array of drawing capabilities. In this "one-hour" excerpt by James Foxall and Wendy Haro-Chun, you learn the basic skills for drawing shapes and text to a form or other graphical surface.

C# provides an amazingly powerful array of drawing capabilities. However, this power comes at the price of a steep learning curve. Drawing isn't intuitive; you can't sit down for a few minutes with the online Help text and start drawing graphics. After you learn the basic principles involved, however, you'll find that drawing isn't that complicated. In this hour, you'll learn the basic skills for drawing shapes and text to a form or other graphical surface. You'll learn about pens, colors, and brushes. In addition, you'll learn how to persist graphics on a form—and even how to create bitmaps that exist solely in memory.

The highlights of this hour include the following:

  • Understanding the Graphics object
  • Working with pens
  • Using system colors
  • Working with rectangles
  • Drawing shapes
  • Drawing text
  • Persisting graphics on a form

Understanding the Graphics Object

The code within the Windows operating system that handles drawing everything to the screen, including text, lines, and shapes, is called the Graphics Device Interface (GDI). The GDI processes all drawing instructions from applications as well as from Windows itself, and generates the proper output for the current display. Because the GDI generates what you see onscreen, it has the responsibility of dealing with the particular display driver installed on the computer and the settings of the driver, such as resolution and color depth. This means that applications don't have to worry about these details; you write code that tells the GDI what to output, and the GDI does whatever is necessary to produce that output. This behavior is called device independence because applications can instruct the GDI to display text and graphics using code that is independent of the particular display device.

C# code communicates with the GDI primarily via a Graphics object. The basic process is the following:

  • An object variable is created to hold a reference to a Graphics object.

  • The object variable is set to a valid Graphics object (new or existing).

  • To draw or print, you call methods of the Graphics object.

Creating a Graphics Object for a Form or Control

If you want to draw directly to a form or control, you can easily get a reference to the drawing surface by calling the CreateGraphics() method of the object in question. For example, to create a Graphics object that draws to a text box, you could use code such as this:

System.Drawing.Graphics objGraphics; objGraphics = this.textBox1.CreateGraphics();

When you call CreateGraphics(), you're setting the object variable to hold a reference to the Graphics object of the form or control's client area. The client area of a form is the gray area within the borders and title bar of the form. The client area of a control is usually the entire control. All drawing and printing done using the Graphics object is sent to the client area. In the code shown previously, the Graphics object references the client area of a text box, so all drawing methods called on the Graphics object would draw on the text box only.

NOTE

When you draw directly to a form or control, the object in question doesn't persist what is drawn on it. If the form is obscured in any way, such as by a window covering it or by minimizing the form, the next time the form is painted, it won't contain anything that was drawn on it. Later in this hour, I'll teach you how to persist graphics on a form.

Creating a Graphics Object for a New Bitmap

You don't have to set a Graphics object to the client area of a form or control; you can also set a Graphics object to a bitmap that exists only in memory. For performance reasons, you might want to use a memory bitmap to store temporary images or to use as a place to build complex graphics before sending them to a visible element. To do this, you first have to create a new bitmap. To create a new bitmap, you declare a variable to hold a reference to the new bitmap and then you create a new bitmap using the following syntax:

Bitmap variable = new Bitmap(width, height, pixelformat);

The width and height arguments are exactly what they appear to be: the width and height of the new bitmap. The pixelformat argument, however, is less intuitive. This argument determines the color depth of the bitmap and may also specify whether the bitmap has an alpha layer (used for transparent portions of bitmaps). Table 1 lists a few of the common values for PixelFormat (see C#'s online Help for the complete list of values and their meanings). Note that the pixelformat parameter is referenced as System.Drawing.Imaging.PixelFormat.formatenumeration.

Table 1—Common Values for PixelFormat

Value Description
Format16bppGrayScale The pixel format is 16 bits per pixel. The color information specifies 65,536 shades of gray.
Format16bppRgb555 The pixel format is 16 bits per pixel. The color information specifies 32,768 shades of color, of which five bits are red, five bits are green, and five bits are blue.
Format24bppRgb The pixel format is 24 bits per pixel. The color information specifies 16,777,216 shades of color, of which eight bits are red, eight bits are green, and eight bits are blue.

For example, to create a new bitmap that is 640 pixels wide by 480 pixels tall and has a pixel depth of 24 bits, you could use the following statement:

objMyBitMap = new Bitmap(640, 480,     System.Drawing.Imaging.PixelFormat.Format24bppRgb);

After the bitmap is created, you can create a Graphics object that references the bitmap using the FromImage() method, like this:

objGraphics = Graphics.FromImage(objMyBitMap);

Now, any drawing or printing done using objGraphics would be performed on the memory bitmap. For the user to see the bitmap, you'd have to send the bitmap to a form or control. You'll do this in the section "Persisting Graphics on a Form."

Disposing of an Object

When you're finished with a Graphics object, you should call its Dispose() method to ensure that all resources used by the Graphics object are freed. Simply letting an object variable go out of scope doesn't ensure that the resources used by the object are freed. Graphics objects can use considerable resources, so you should always call Dispose() when you're finished with any Graphics object (including Pens and other types of objects).

C# also supports a way of automatically disposing object resources. This can be accomplished utilizing C#'s using statement. The using statement wraps a declared object or objects in a block and disposes of those objects once the block is done. As a result, after the code is executed in a block, the block is exited, and the resources are disposed of upon exit. Following is the syntax for the using statement and a small sample:

using (expression | type identifier = initializer) {   // Statements to execute  }  using (MyClass objClass = new MyClass()) {   objClass.Method1();   objClass.Method2(); }

One thing to keep in mind is that the using statement acts as a wrapper for an object within a specified block of code; therefore, it is only useful for declaring objects that are used and scoped within a method.

Working with Pens

A pen is an object that defines line-drawing characteristics. Pens are used to define color, line width, and line style (solid, dashed, and so on), and pens are used with almost all the drawing methods you'll learn about in this hour.

C# supplies a number of predefined pens, and you can also create your own. To create your own pen, use the following syntax:

Pen variable = new Pen(color, width);

After a pen is created, you can set its properties to adjust its appearance. For example, all Pen objects have a DashStyle property that determines the appearance of lines drawn with the pen. Table 2 lists the possible values for DashStyle.

Table 2—Possible Values for DashStyle

Value

Description

Dash

Specifies a line consisting of dashes.

DashDot

Specifies a line consisting of a pattern of dashes and dots.

DashDotDot

Specifies a line consisting of alternating dashes and double dots.

Dot

Specifies a line consisting of dots.

Solid

Specifies a solid line.

Custom

Specifies a custom dash style. The Pen object contains properties that can be used to define the custom line.


The enumeration for DashStyle is part of the Drawing.Drawing2D object. Therefore, to create a new pen and use it to draw an ellipse, for example, you could use the following code:

Pen objMyPen = new Pen(System.Drawing.Color.DarkBlue, 3); objMyPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;

C# includes many standard pens, which are available via the System.Drawing.Pens class, as in the following:

objPen = System.Drawing.Pens.DarkBlue;

When drawing using the techniques discussed shortly, you can use custom pens or system-defined pens—it's your choice.

Using System Colors

At some point, you may have changed your Windows theme, or perhaps you changed the image or color of your desktop. What you may not be aware of is that Windows enables you to customize the colors of almost all Windows interface elements. The colors that Windows allows you to change are called system colors. To change your system colors, right-click the desktop and choose Properties from the shortcut menu to display the Display Properties dialog box, and then click the Appearance tab (see Figure 1). To change the color for a specific item, you can select the item from the Item drop-down list, or you can click the element in the top half of the tab.

NOTE

On Windows XP, you'll need to click Advanced on the Appearance tab to change your system colors.

Figure 1 The Display Properties dialog box lets you select the colors of most Windows interface elements.

When you change a system color using the Display Properties dialog box, all loaded applications should change their appearance to match your selection. In addition, when you start any new applications, they should also match their appearance to your selection. If you had to write code to manage this behavior, you would have to write a lot of code, and you would be justified in avoiding the whole thing. However, making an application adjust its appearance to match the user's system color selections is actually quite trivial; therefore, there's no reason not to do it.

To designate that an interface color should stay in sync with a user's system colors, you assign a system color to a color property of the item in question (see Figure 2). Table 3 lists the system colors you can use. For example, if you wanted to ensure that the color of a button matches the user's corresponding system color, you would assign the system color named Control to the BackColor property of the Button control.

Figure 2 System colors are assigned using the System palette tab.

Fortunately, when you create new forms and when you add controls to forms, C# automatically assigns the proper system color to the appropriate properties. Another good thing is that when a user changes a system color using the Display Properties dialog box, C# automatically updates the appearance of objects that use system colors; you don't have to write a single line of code to do this.

Be aware that you aren't limited to assigning system colors to their logically associated properties. You can assign system colors to any color property you want, and you can also use system colors when drawing. This allows you, for example, to draw custom interface elements that match the user's system colors. Be aware, however, that if you do draw with system colors, C# won't update the colors automatically when the user changes system colors; you would have to redraw the elements with the new system color.

NOTE

Users don't just change their system colors for aesthetic purposes. I work with a programmer who is colorblind. He's modified his system colors so that he can see things better on the screen. If you don't allow your applications to adjust to the color preferences of the user, you may make using your program unnecessarily difficult, or even impossible, for someone with color blindness.

Table 3—Properties of the SystemColors Class

Enumeration

Description

ActiveBorder

The color of the filled area of an active window border.

ActiveCaption

The color of the background of an active caption bar (title bar).

ActiveCaptionText

The color of the text of the active caption bar (title bar).

AppWorkspace

The color of the application workspace. The application workspace is the area in a multiple-document view that is not being occupied by child windows.

Control

The color of the background of push buttons and other 3D elements.

ControlDark

The color of shadows on a 3D element.

ControlDarkDark

The color of darkest shadows on a 3D element.

ControlLight

The color of highlights on a 3D element.

ControlLightLight

The color of lightest highlights on a 3D element.

ControlText

The color of the text on buttons and other 3D elements.

Desktop

The color of the Windows desktop.

GrayText

The color of the text on a user-interface element when it's unavailable.

Highlight

The color of the background of highlighted text. This includes selected menu items as well as selected text.

HighlightText

The color of the foreground of highlighted text. This includes selected menu items as well as selected text.

HotTrack

The color used to represent hot tracking.

InactiveBorder

The color of an inactive window border.

InactiveCaption

The color of the background of an inactive caption bar.

InactiveCaptionText

The color of the text of an inactive caption bar.

Info

The color of the background of the ToolTip.

InfoText

The color of the text of the ToolTip.

Menu

The color of the menu background.

MenuText

The color of the menu text.

ScrollBar

The color of the scrollbar background.

Window

The color of the background in the client area of a window.

WindowFrame

The color of the frame around a window.

WindowText

The color of the text in the client area of a window.


Working with Rectangles

Before learning how to draw shapes, you need to understand the concept of a rectangle as it relates to C# programming. A rectangle isn't necessarily used to draw a rectangle (although it can be). Rather, a rectangle is a structure used to hold bounding coordinates used to draw a shape. Obviously, a square or rectangle can fit within a rectangle. However, so can circles and ellipses. Figure 3 illustrates how most shapes can be bound by a rectangle.

Figure 3 Rectangles are used to define the bounds of most shapes.

To draw most shapes, you must have a rectangle. The rectangle you pass to a drawing method is used as a bounding rectangle; the proper shape (circle, ellipse, and so on) is always drawn. Creating a rectangle is easy. First, you dimension a variable as Rectangle and then you set the X, Y, Width, and Height properties of the object variable. The X, Y value is the coordinate of the upper-left corner of the rectangle. For example, the following code creates a rectangle that has its upper-left corner at coordinate 0,0, has a width of 100, and a height of 50:

Rectangle rectBounding = new Rectangle(); rectBounding.X = 0; rectBounding.Y = 0; rectBounding.Width = 100; rectBounding.Height = 50;

The Rectangle object enables you to send the X, Y, Height, and Width values as part of its initialize construct. Using this technique, you could create the same rectangle with only a single line of code:

 Rectangle rectBounding = new Rectangle(0,0,100,50);

You can do a number of things with a rectangle after it's defined. Perhaps the most useful is the capability to enlarge or shrink the rectangle with a single statement. You enlarge or shrink a rectangle using the Inflate() method. The most common syntax of Inflate() is the following:

object.Inflate(changeinwidth, changeinheight);

When called this way, the rectangle width is enlarged (the left side of the rectangle remains in place) and the height is enlarged (the top of the rectangle stays in place). To leave the size of the height or width unchanged, pass 0 as the appropriate argument. To shrink a dimension, specify a negative number.

If you're going to do much with drawing, you'll use a lot of Rectangle objects, and I strongly suggest that you learn as much about them as you can.

Drawing Shapes

Now that you've learned about the Graphics object, pens, and rectangles, you'll probably find drawing shapes to be fairly simple. Shapes are drawn by calling methods of a Graphics object. Most methods require a rectangle, which is used as the bounding rectangle for the shape, as well as a pen. In this section, I'll show you what you need to do to draw different shapes.

NOTE

I've chosen to discuss only the most commonly drawn shapes. The Graphics object contains many methods for drawing additional shapes.

Drawing Lines

Drawing lines is accomplished with the DrawLine() method of the Graphics object. DrawLine() is one of the few drawing methods that doesn't require a rectangle. The syntax for DrawLine() is

object.DrawLine(pen, x1, y1, x2, y2);

Object refers to a Graphics object and pen refers to a Pen object, both of which have already been discussed. X1, Y1 is the coordinate of the starting point of the line, whereas X2, Y2 is the coordinate of the ending point; C# draws a line between the two points, using the specified pen.

Drawing Rectangles

Drawing rectangles (and squares for that matter) is accomplished using the DrawRectangle() method of a Graphics object. As you might expect, DrawRectangle() accepts a pen and a rectangle. Following is the syntax for calling DrawRectangle() in this way:

object.DrawRectangle(pen, rectangle);

If you don't have a Rectangle object (and you don't want to create one), you can call DrawRectangle() using the following format:

object.DrawRectangle(pen, X, Y, width, height);

Drawing Circles and Ellipses

Drawing circles and ellipses is accomplished by calling the DrawEllipse() method. If you're familiar with geometry, you'll note that a circle is simply an ellipse that has the same height as it does width. This is why no specific method exists for drawing circles; DrawEllipse() works perfectly. Like the DrawRectangle() method, DrawEllipse() accepts a Pen and a Rectangle. The rectangle is used as a bounding rectangle—the width of the ellipse is the width of the rectangle, whereas the height of the ellipse is the height of the rectangle. DrawEllipse() has the following syntax:

object.DrawEllipse(pen, rectangle);

In the event that you don't have a Rectangle object defined (and again you don't want to create one), you can call DrawEllipse() with this syntax:

object.DrawEllipse(pen, X, Y, Width, Height);

Clearing a Drawing Surface

To clear the surface of a Graphics object, call the Clear() method, passing it the color to paint the surface, like this:

objGraphics.Clear(Drawing.SystemColors.Control);

Drawing Text

Printing text on a Graphics object is very similar to drawing a shape, and the method name even contains the word Draw, in contrast to Print. To draw text on a Graphics object, call the DrawString() method. The basic format for DrawString() looks like this:

object.DrawString(stringoftext, font, brush, topX, leftY);

A few of these items are probably new to you. The argument stringoftext is fairly self-explanatory; it's the string you want to draw on the Graphics object. The topX and leftY arguments represent the coordinate at which drawing will take place; they represent the upper-left corner of the string, as illustrated in Figure 4.

Figure 4 The coordinate specified in DrawString() represents the upper-left corner of the printed text.

The arguments brush and font aren't so obvious. Both arguments accept objects. A brush is similar to a pen. However, whereas a pen describes the characteristics of a line, a brush describes the characteristics of a fill. For example, both pens and brushes have a color, but where pens have an attribute for defining a line style, such as dashed or solid, a brush has an attribute for a fill pattern, such as solid, hatched, weave, or trellis. When drawing text, a solid brush is usually sufficient. You can create brushes in much the same way as you create pens, or you can use one of the standard brushes available from the System.Drawing.Brushes class.

A Font object defines characteristics used to format text, including the character set (Times New Roman, Courier, for example), size (point size), and style (bold, italic, normal, underlined, and so on). To create a new Font object, you could use code such as the following:

Font objFont; objFont = new System.Drawing.Font("Arial", 30);

The text Arial in this code is the name of a font installed on my computer. In fact, Arial is one of the few fonts installed on all Windows computers. If you supply the name of a font that doesn't exist, C# will use a default font. The second parameter is the point size of the text. If you want to use a style other than normal, you can provide a style value as a third parameter, like this:

objFont = new System.Drawing.Font("Arial Black", 30,FontStyle.Bold);

or

objFont = new System.Drawing.Font("Arial Black", 30,FontStyle.Italic);

In addition to creating a Font object, you can also use the font of an existing object, such as a Form. For example, the following statement prints text to a Graphics object using the font of the current form:

objGraphics.DrawString("This is the text that prints!",              this.Font,System.Drawing.Brushes.Azure, 0, 0);

Persisting Graphics on a Form

You'll often use the techniques discussed in this hour to draw to a form. However, you may recall from earlier hours that when you draw to a form (actually, you draw to a Graphics object that references a form), the things that you draw aren't persisted; the next time the form paints itself, the drawn elements will disappear. For example, if the user minimizes the form or obscures the form with another window, the next time the form is painted, it will be missing any and all drawn elements that were obscured. You can use a couple of approaches to deal with this behavior:

  • Place all code that draws to the form in the form's Paint event.

  • Draw to a memory bitmap and copy the contents of the memory bitmap to the form in the form's Paint event.

If you're drawing only a few items, placing the drawing code in the Paint event might be a good approach. However, consider a situation in which you've got a lot of drawing code. Perhaps the graphics are drawn in response to user input, so you can't re-create them all at once. In these situations, the second approach is clearly better.

Build a Graphics Project Example

You're now going to build a project that uses the skills you've already learned to draw to a form. In this project, you'll use the technique of drawing to a memory bitmap to persist the graphics each time the form paints itself.

NOTE

The project you're about to build is perhaps the most difficult yet. I'll explain each step of the process of creating this project, but I won't spend any time explaining the objects and methods that I've already discussed.

To make things interesting, I've used random numbers to determine font size as well as the X, Y coordinate of the text you're going to draw to the form. The Random class and its Next() method will be used to generate pseudo-random numbers. To generate a random number within a specific range (such as a random number between 1 and 10), you use the following:

randomGenerator.Next(1,10);

I don't want you to dwell on the details of how the ranges of random numbers are created. However, at times, you may need to use a random number, so I thought I'd spice things up a bit and teach you something cool at the same time.

Start by creating a new Windows Application titled Persisting Graphics.

Change the name of the default form to fclsMain, set the form's Text property to Persisting Graphics, and change the entry point of the project to reference fclsMain instead of Form1. The interface of your form will consist of a text box and a button. When the user clicks the button, the contents of the text box will be drawn on the form in a random location and a random font size. Add a new text box to your form and set its properties as follows:

Property

Value

Name
txtInput
Location
56,184
Size
100,20
Text
(make blank)

Add a new button to the form and set its properties as follows:

Property

Value

Name
btnDrawText
Location
160,184
Text
Draw Text

Let the code fly!

As I mentioned earlier, all drawing is going to be performed using a memory bitmap, which will then be drawn on the form. You will reference this bitmap in multiple places, so you're going to make it a module-level variable by following these steps:

  1. Double-click the Form to access its Load event.

  2. Locate the statement public class fclsMain : System.Windows.Forms.Form and position your cursor immediately after the left bracket ({) on the next line.

  3. Press Enter to create a new line.

  4. Enter the following statement:

    private System.Drawing.Bitmap m_objDrawingSurface;

For the bitmap variable to be used, it must reference a Bitmap object. A good place to initialize things is in the form's Load event, so put your cursor back in the Load event now and enter the following code:

// Create a drawing surface with the same dimensions as the client // area of the form. m_objDrawingSurface = new Bitmap(this.ClientRectangle.Width,   this.ClientRectangle.Height,_   System.Drawing.Imaging.PixelFormat.Format24bppRgb); InitializeSurface();

Your procedure should now look like the one shown in Figure 5.

Figure 5 Make sure your code appears exactly as it does here.

The first statement creates a new bitmap in memory. Because the contents of the bitmap are to be sent to the form, it makes sense to use the dimensions of the client area of the form as the size of the new bitmap—which is exactly what you've done. The final statement calls a procedure that you haven't yet created.

Position the cursor after the closing bracket (}) of the fclsMain_Load event and press Enter to create a new line. You're now going to write code to initialize the bitmap. The code will clear the bitmap to the system color named Control and then draw an ellipse that has the dimensions of the bitmap. (I've added comments to the code so that you can follow along with what's happening; all the concepts in this code have been discussed already.) Enter the following in its entirety:

private void InitializeSurface() {   Graphics objGraphics;   Rectangle rectBounds;       // Create a Graphics object that references the bitmap and clear it.   objGraphics = Graphics.FromImage(m_objDrawingSurface); 		   objGraphics.Clear(SystemColors.Control);    //Create a rectangle the same size as the bitmap.   rectBounds = new Rectangle(0, 0,         m_objDrawingSurface.Width,m_objDrawingSurface.Height);   //Reduce the rectangle slightly so the ellipse won't appear on the border.   rectBounds.Inflate(-1, -1);    // Draw an ellipse that fills the form.   objGraphics.DrawEllipse(Pens.Orange, rectBounds);    // Free up resources.   objGraphics.Dispose(); }

Your procedure should now look like the one shown in Figure 6.

Figure 6 Again, verify that your code is entered correctly.

If you run your project now, you'll find that nothing is drawn to the form. This is because the drawing is being done to a bitmap in memory, and you haven't yet added the code to copy the bitmap to the form. The place to do this is in the form's Paint event so that the contents of the bitmap are sent to the form every time the form paints itself. This ensures that the items you draw always appear on the form.

Create an event handler for the form's Paint event by first returning to the form designer and selecting the form. Click the Event icon (the lightning bolt) in the Properties window and then double-click Paint to create a Paint event . Add the following code to the Paint event:

Graphics objGraphics ;  //You can't modify e.Graphics directly. objGraphics = e.Graphics; // Draw the contents of the bitmap on the form. objGraphics.DrawImage(m_objDrawingSurface, 0,0,   m_objDrawingSurface.Width,   m_objDrawingSurface.Height); objGraphics.Dispose();

NOTE

The previous code can be rewritten as follows when utilizing the using statement mentioned earlier in this chapter, notice how the Dispose() method is not required anymore.

   using (Graphics objGraphics = e.Graphics)    {    objGraphics.DrawImage(m_objDrawingSurface,0,0,       m_objDrawingSurface.Width,      m_objDrawingSurface.Height);    }

The e parameter of the Paint event has a property that references the Graphics object of the form. However, you can't modify the Graphics object using the e parameter (it's read-only), which is why you've created a new Graphics object with which to work and then set the object to reference the form's Graphics object. The method DrawImage() draws the image in a bitmap to the surface of a Graphics object, so the last statement is simply sending the contents of the bitmap to the form.

If you run the project now, you'll find that the ellipse appears on the form. Furthermore, you can cover the form with another window or even minimize it, and the ellipse will always appear on the form when it's displayed again.

The last thing you're going to do is write code that draws the contents entered into the text box on the form. The text will be drawn with a random size and location. Return to the form designer and double-click the button to access its Click event. Add the following code:

Graphics objGraphics;  Font objFont;  int intFontSize, intTextX, intTextY; 			 Random randomGenerator = new Random();  			 // If no text has been entered, get out. if (txtInput.Text == "") return;  // Create a graphics object using the memory bitmap. objGraphics = Graphics.FromImage(m_objDrawingSurface);  // Create a random number for the font size. Keep it between 8 and 48. intFontSize = randomGenerator.Next(8,48); // Create a random number for the X coordinate of the text. intTextX = randomGenerator.Next(0,this.ClientRectangle.Width); // Create a random number for the Y coordinate of the text. intTextY = randomGenerator.Next(0,this.ClientRectangle.Height);  // Create a new font object. objFont = new System.Drawing.Font("Arial", intFontSize, FontStyle.Bold); // Draw the user's text. objGraphics.DrawString(txtInput.Text, objFont, System.Drawing.Brushes.Red, intTextX, intTextY); // Clean up. objGraphics.Dispose(); // Force the form to paint itself. This triggers the Paint event. this.Invalidate();

The comments I've included should make the code fairly self-explanatory. However, the last statement bears discussing. The Invalidate() method of a form invalidates the client rectangle. This operation tells Windows that the appearance of the form is no longer accurate and that the form needs to be repainted. This, in turn, triggers the Paint event of the form. Because the Paint event contains the code that copies the contents of the memory bitmap to the form, invalidating the form causes the text to appear. If you don't call Invalidate() here, the text won't appear on the form (but it is still drawn on the memory bitmap).

NOTE

If you draw elements that are based on the size of the form, you'll need to call Invalidate() in the Resize event of the form; resizing a form doesn't trigger the form's Paint event.

The last thing you need to do is make sure you free up the resources used by your module-level Graphics object. Add an event handler for the Closed event of the form now using the Properties window, and enter the following statement:

m_objDrawingSurface.Dispose();
 

Your project is now complete! Click Save All on the toolbar to save your work, and then press F5 to run the project. You'll notice immediately that the ellipse is drawn on the form. Type something into the text box and click the button. Click it again. Each time you click the button, the text is drawn on the form using the same brush, but with a different size and location (see Figure 7).

Figure 7 Text is drawn on a form, much like ordinary shapes.

Summary

You won't need to add drawing capabilities to every project you create. However, when you need the capabilities, you need the capabilities. In this hour, you learned the basic skills for drawing to a graphics surface, which can be a form, control, memory bitmap, or one of many other types of surfaces. You learned that all drawing is done using a Graphics object, and you now know how to create a Graphics object for a form or control and even how to create a Graphics object for a bitmap that exists in memory.

Most drawing methods require a pen and a rectangle, and you can now create rectangles and pens using the techniques you learned in this hour. After learning pens and rectangles, you've found that the drawing methods themselves are pretty easy to use. Even drawing text is a pretty easy process when you've got a Graphics object to work with.

Persisting graphics on a form can be a bit complicated, and I suspect this will confuse a lot of new C# programmers who try to figure it out on their own. However, you've now built an example that persists graphics on a form, and you'll be able to leverage the techniques involved when you have to do this in your own projects.

I don't expect you to be able to sit down for an hour and create an Adobe Photoshop knock-off. However, you now have a solid foundation on which to build. If you're going to attempt a project that performs a lot of drawing, you'll want to dig deeper into the Graphics object.

Q&A

What if I need to draw a lot of lines, one starting where another ends? Do I need to call DrawLine() for each line?

The Graphics object has a method called DrawLines(), which accepts a series of points. The method draws lines connecting the sequence of points.

Is there a way to fill a shape?

The Graphics object includes methods that draw filled shapes, such as FillEllipse() and FillRectangle().

Workshop

Quiz

1. What object is used to draw to a surface?

2. To set a Graphics object to draw to a form directly, you call what method of the form?

3. What object defines the characteristics of a line? A fill pattern?

4. How do you make a color property adjust with the user's Windows settings?

5. What object is used to define the bounds of a shape to be drawn?

6. What method do you call to draw an irregular ellipse? A circle?

7. What method do you call to print text on a Graphics surface?

8. To ensure that graphics persist on a form, the graphics must be drawn on the form in what event?

Exercises

  1. Modify the example in this hour to use a font other than Arial. If you're not sure what fonts are installed on your computer, open the Start menu and choose Settings and then Control Panel. You'll have an option on the Control Panel for viewing your system fonts.

  2. Create a project that draws an ellipse that fills the form, much like the one you created in this hour. However, draw the ellipse directly to the form in the Paint event. Make sure that the ellipse is redrawn when the form is sized. (Hint: Invalidate the form in the form's Resize event.)

Answers

1. What object is used to draw to a surface?

		Graphics

2. To set a Graphics object to draw to a form directly, you call what method of the form?

		CreateGraphics()

3. What object defines the characteristics of a line? A fill pattern?

Pens define lines, brushes define fill patterns.

4. How do you make a color property adjust with the user's Windows settings?

Assign a system color to the property

5. What object is used to define the bounds of a shape to be drawn?

		Rectangle

6. What method do you call to draw an irregular ellipse? A circle?

Ellipses and circles are both drawn using the DrawEllipse() method.

7. What method do you call to print text on a Graphics surface?

		DrawString()

8. To ensure that graphics persist on a form, the graphics must be drawn on the form in what event?

The form's Paint event




--
Alain Lompo
Excelta - Conseils et services informatiques
MCT
MCSD For Microsoft .Net
MVP Windows Systems Server / Biztalk Server
Certifié ITIL et Microsoft Biztalk Server

Aucun commentaire: