Using the Graphics Class

Using the Graphics Class

The Graphics class is one of the fundamental classes used in programming with Windows Forms and GDI+. Through the Graphics class, you gain access to information about the display resolution and device capabilities. In this section, you’ll learn about the Paint event and how it supplies you with a reference to a properly initialized Graphics object. This section also covers the most common operations performed using the Graphics object, including drawing text and drawing and filling shapes such as rectangles and ellipses.

Handling the Paint Event

In a Windows Forms application, controls and forms are updated in response to Paint events. Much of the time, you might not even be aware that a Paint event has occurred. In the examples presented in Chapter 12 and Chapter 13, the user interface for each application consisted entirely of controls that encapsulated their own drawing code. Internally, however, the controls were responding to Paint events.

Paint events are handled by PaintEventHandler delegates, which are defined as follows:

Paint += new PaintEventHandler(this.Form_Paint);

    

private void Form_Paint(object sender,
                        System.Windows.Forms.PaintEventArgs e)
{
    
    

PaintEventHandler delegates accept a PaintEventArgs parameter that contains the event data. The PaintEventArgs parameter contains the following additional properties that are essential for handling the Paint event:

  • Graphics  Returns an instance of the Graphics class to be used for output

  • ClipRectangle  Returns the invalid rectangle to be painted

The Graphics class is used throughout the remainder of this chapter to display output. This class defines all the methods and properties used to draw lines, text, and shapes on the display. It also can be used to gather information about the display’s characteristics, such as screen resolution and color depth.

Drawing Text with GDI+

The console programs created in Part I and Part II of this book used the Console.Write and Console.WriteLine methods to display text output. Windows Forms applications don’t send output to a simple console—their output is displayed on forms in a much richer format than the display options permitted by the System.Console class.

When text is displayed on a Windows form or control, the text display is generated in a Paint event handler, using one of the six overloaded versions of the DrawString method. Each overloaded version of DrawString enables you to specify the string to be displayed, the font used to display the text, and a brush that defines the text color and pattern. The first form of the DrawString method is the simplest and uses a PointF value to specify the location of the text, as shown here:

PointF textPoint = new PointF(12.0f, 12.0f);
e.Graphics.DrawString("Text location specified by a PointF value",
                       Font,
                       SystemBrushes.WindowText,
                       textPoint); 

The second version of DrawString enables you to pass floating-point values that specify the location of the text, as shown here:

e.Graphics.DrawString("Text location specified by float values",
                       Font,
                       SystemBrushes.WindowText,
                       12.0f,
                       12.0f); 

The problem with these two versions of DrawString is that text is drawn in a single line from the specified point, without regard for the enclosing form. When you’re drawing a longer string, as in the following example, which uses a portion of text from Shakespeare’s Henry V, the string simply runs off the edge of the form:

string crispinSpeech = 
    "We few, we happy few, we band of brothers. " +
    "For he today that sheds his blood with me " +
    "shall be my brother, be he ne'er so vile, " +
    "this day shall gentle his condition. " +
    "And gentlemen in England now abed " +
    "shall think themselves accursed they were not here, " +
    "and hold their manhoods cheap whiles any speaks " +
    "that fought with us upon Saint Crispin's day.";

PointF textPoint = new PointF(12.0f, 12.0f);
e.Graphics.DrawString(crispinSpeech,
                      Font,
                      SystemBrushes.WindowText,
                      textPoint);

The third version of DrawString allows you to pass a RectangleF value to DrawString that defines a bounding rectangle for the text, which will force the text to be wrapped within it, as shown here:

string crispinSpeech = 
    "We few, we happy few, we band of brothers. " +
    "For he today that sheds his blood with me " +
    "shall be my brother, be he ne'er so vile, " +
    "this day shall gentle his condition. " +
    "And gentlemen in England now abed " +
    "shall think themselves accursed they were not here, " +
    "and hold their manhoods cheap whiles any speaks " +
    "that fought with us upon Saint Crispin's day. ";

RectangleF boundingRect = new RectangleF(12.0f,
                                         100.0f,
                                         180.0f,
                                         180.0f);
e.Graphics.DrawString(crispinSpeech,
                      Font,
                      SystemBrushes.WindowText,
                      boundingRect); 

The remaining three versions of the DrawString method are similar to the first three versions except that they include a StringFormat parameter that supplies text layout options. For example, in addition to a StringFormat object, the fourth version of DrawString uses a PointF value to define the upper-left coordinate of the text’s bounding rectangle, as shown here:

StringFormat stringFormat = new StringFormat();
stringFormat.Alignment = StringAlignment.Center;
stringFormat.LineAlignment = StringAlignment.Center;
e.Graphics.DrawString(crispinSpeech,
                      Font,
                      SystemBrushes.WindowText,
                      textPoint,
                      stringFormat); 

The StringFormat class is used to specify text layout options for DrawString and other methods. The StringFormat class has four constructors. The simplest version accepts no parameters and creates an object with default properties, as shown here:

StringFormat format = new StringFormat();

When this constructor is used, any nondefault characteristics must be set using properties or methods exposed by the StringFormat class.

The second StringFormat constructor accepts an existing StringFormat object as a parameter and creates a new StringFormat object with the same characteristics, as shown here:

StringFormat format = new StringFormat(otherFormat);

The third StringFormat constructor accepts a value from the StringFormatFlags enumeration, as shown here:

StringFormat format = new StringFormat(StringFormatFlags.NoClip);

The fourth version of the constructor is used to specify the language used when determining the string formatting and won’t be discussed here. (Refer to the Microsoft Developer Network (MSDN) Library for a detailed discussion of this version.)

The StringFormatFlags enumeration is used to specify formatting options for StringFormat objects. The enumeration values are listed in Table 14-1 and can be combined with the OR operator.

The StringFormat class exposes properties that are used to control the formatting options. The most commonly used properties are listed here:

  • Alignment  A value from the StringAlignment enumeration that specifies horizontal text alignment

  • FormatFlags  A value from the StringFormatFlags enumeration (see Table 14-1) that specifies formatting options

  • LineAlignment  A value from the StringAlignment enumeration that specifies vertical text alignment

Table 14-2 lists the values from the StringAlignment enumeration.

Drawing Lines

The Graphics class includes methods that are used to draw lines and curves. The simplest of these is DrawLine, which is used to connect two points. There are four versions of DrawLine; these versions differ only in the way that line endpoints are specified. The following version of DrawLine uses two Point values:

Point topLeft = ClientRectangle.Location;
Point bottomRight = new Point(ClientRectangle.Right,
                              ClientRectangle.Bottom);
e.Graphics.DrawLine(SystemPens.WindowText,
                    topLeft,
                    bottomRight); 

This example draws a line from the upper-left corner of the client rectangle to the lower-right corner. The line is described with a Pen object, which includes properties that specify the line’s style, color, and width. The call to SystemPens.WindowText obtains a reference to a Pen object that has the color selected by the user for window text. Pen objects will be discussed later in this chapter, in the section “Drawing with Pens.”

The second version of DrawLine uses PointF values to define endpoints, as shown here:

PointF topLeft = new PointF(0.0f, 0.0f);
PointF bottomRight = new PointF(208.5f, 306.2f);
e.Graphics.DrawLine(SystemPens.WindowText,
                    topLeft,
                    bottomRight);  

The remaining two versions of DrawLine enable you to define endpoints using discrete floating-point or integer values.

The DrawLines method is used to draw multiple line segments with a single method call, with the endpoints passed as an array of Point or PointF values, as shown here:

Point [] pointArray = new Point [] {
                                        new Point(10, 10),
                                        new Point(10, 200),
                                        new Point(200, 200),
                                        new Point(200, 10),
                                        new Point(10, 10)
                                    };
e.Graphics.DrawLines(SystemPens.WindowText,
                     pointArray); 
Drawing Rectangles

The Graphics class provides the following two methods that are used to draw rectangles:

  • DrawRectangle  Draws a single rectangle

  • DrawRectangles  Draws multiple rectangles

These two methods are similar; the advantage of the DrawRectangles method is that it enables you to incur the overhead of a single method call when you’re drawing multiple rectangles at the same time.

The three overloaded versions of DrawRectangle each accept a Pen object and the dimensions of the rectangle to be drawn as parameters. The difference between the versions is in how the rectangle dimensions are communicated to the DrawRectangle method. The first version of DrawRectangle accepts a Rectangle structure as a parameter, as shown here:

Rectangle theBox = new Rectangle(15, 15, 200, 200);
e.Graphics.DrawRectangle(SystemPens.WindowText, theBox); 

The second version of DrawRectangle accepts four integer values, as shown here:

int top = 15;
int left = 15;
int width = 200;
int height = 200;
e.Graphics.DrawRectangle(SystemPens.WindowText,
                         left,
                         top,
                         width,
                         height); 

The integers passed as parameters represent (in order) the rectangle’s left x-coordinate, the rectangle’s top y-coordinate, the width of the rectangle, and the height of the rectangle.

The third version of DrawRectangle uses float parameters to specify the dimensions of the rectangle, as shown here:

float top = 15.0f;
float left = 15.0f;
float width = 200.0f;
float height = 200.0f;
e.Graphics.DrawRectangle(SystemPens.WindowText,
                         left,
                         top,
                         width,
                         height);  

When you need to draw multiple rectangles, it’s often more efficient to arrange your rectangles in an array and call one of two overloaded versions of the DrawRectangles method. As with the DrawRectangle method, the two overloaded versions of DrawRectangles differ only in how the drawing targets are passed as parameters. The DrawRectangles method accepts either Rectangle or RectangleF structures. The following version of DrawRectangles accepts an array of Rectangle structures:

Rectangle [] theBoxes = new Rectangle[] {
                                    new Rectangle(15, 15, 200, 200),
                                    new Rectangle(40, 30, 200, 200),
                                    new Rectangle(100, 100, 200, 200),
                                    new Rectangle(200, 100, 200, 200)
                                    };
e.Graphics.DrawRectangles(SystemPens.WindowText, theBoxes); 
Filling Rectangles

The Graphics class includes two methods that are used to fill rectangles: FillRectangle and FillRectangles. Each of these methods accepts a Brush object as a parameter; the Brush object is used to specify the color and pattern that fill the rectangle. For the examples in this section, we’ll use a solid red brush that’s part of the Brushes class. Creating a reference to this brush requires just one line of code:

Brush brush = Brushes.Red;

This code creates a brush that will fill a rectangle with a solid red color. Later in this chapter, in the section “Using Brushes,” we’ll look at the wide range of brushes that are available to you when developing with GDI+. For now, it’s enough to know that the preceding code will create an object that turns everything it touches red.

There are four versions of the FillRectangle method. In addition to the brush passed to each version of FillRectangle, these methods enable you to describe the rectangle’s boundaries in four different ways, just like the DrawRectangle method discussed in the previous section. In the first version of FillRectangle, you pass a Rectangle object that describes the area to be filled, as shown here:

Rectangle theBox = new Rectangle(15, 15, 200, 200);
e.Graphics.FillRectangle(Brushes.Red, theBox); 

You can also pass FillRectangle a RectangleF value that describes the rectangle to be filled, as shown here:

RectangleF theBox = new RectangleF(15.0f, 15.0f, 200.0f, 200.0f);
e.Graphics.FillRectangle(Brushes.Red, theBox); 

The remaining two versions of FillRectangle accept either integer or floating-point values as coordinates of the rectangle to be filled. As with DrawRectangle, these parameters follow the standard rectangle pattern: left x-coordinate, top y-coordinate, width, and height.

When filling multiple rectangles, you can take advantage of one of the two versions of the FillRectangles method, both of which accept an array of rectangles to be filled. These rectangles can be specified with either Rectangle or RectangleF structures. The following version of FillRectangles accepts Rectangle structures:

Rectangle [] theBoxes = new Rectangle[] {
                               new Rectangle(15, 15, 200, 200),
                               new Rectangle(40, 30, 200, 200),
                               new Rectangle(100, 100, 200, 200),
                               new Rectangle(200, 100, 200, 200)
                                        };
e.Graphics.FillRectangles(Brushes.Red, theBoxes); 

The companion CD includes a Windows Forms example named ClickRects that draws a series of rectangles in the form’s client area. A new rectangle is drawn at the location of each mouse click in the client area. Figure 14-1 shows the ClickRects application after several rectangles have been drawn in the client area.

Figure 14-1.
The ClickRects application after several rectangles have been drawn.

The relevant parts of the ClickRects source code are shown here:

public class MainForm : System.Windows.Forms.Form
{
    ArrayList _points = new ArrayList();
    readonly Size _rectSize = new Size(100, 100);
    Brush [] _brushes = new Brush[] { Brushes.Red,
                                      Brushes.Green,
                                      Brushes.Blue };
    Pen _edgePen = Pens.Black;
    
    

    [STAThread]
    static void Main() 
    {
        Application.Run(new MainForm());
    }

    private void MainForm_Paint(object sender,
                                System.Windows.Forms.PaintEventArgs e)
    {
        int index = 0;
        foreach(Point pt in _points)
        {
            e.Graphics.FillRectangle(_brushes[index],
                                     pt.X,
                                     pt.Y,
                                     _rectSize.Width,
                                     _rectSize.Height);
            e.Graphics.DrawRectangle(_edgePen,
                                     pt.X,
                                     pt.Y,
                                     _rectSize.Width,
                                     _rectSize.Height);
            if(++index == 3)
                index = 0;
        }
    }

    private void MainForm_Click(object sender, System.EventArgs e)
    {
        _points.Add(PointToClient(MousePosition));
        Invalidate();
    }
}

The ClickRects application maintains an ArrayList collection that stores the location of each mouse click. When a Click event is handled in MainForm_Click, the location of the mouse click is added to ArrayList and the client area is invalidated. When the form receives a Paint event, a rectangle is displayed on the form at each location retrieved from the array.



Part III: Programming Windows Forms