Chapter 30. Drawing Basics

Visual Basic .NET provides a large assortment of objects for drawing and for controlling drawing attributes. The Graphics object provides methods that enable you to draw and fill rectangles, ellipses, polygons, curves, lines, and other shapes. Pen and Brush objects determine the appearance of lines (solid, dashed, dotted) and filled areas (solid colors, hatched, filled with a color gradient).

This chapter provides an overview of the drawing process and a survey of the most important drawing namespaces and their classes. It describes in detail the most central of these classes, the Graphics object, and provides examples showing how to use it. You can download example programs demonstrating most of the methods described in this chapter on the book's web site. The examples also include code to draw the figures in this chapter.

Chapter 31, "Brushes, Pens, and Paths," describes some of the other important drawing classes in greater detail.

DRAWING OVERVIEW

Whenever you draw something in Visual Basic, you must use a Graphics object. This object represents the surface where you are drawing, whether it is a PictureBox, Form, or PrintDocument. Sometimes you will have to create a Graphics object, and other times (as in a Paint event handler) one is provided for you.

The Graphics Device Interface+ (GDI+), or the .NET version of GDI drawing routines, uses two classes, Pen and Brush, to determine the appearance of lines and filled shapes.

A Pen object determines how lines are drawn. A Pen sets a line's color, thickness, dash style, end cap style, and other properties. The Pen applies to all lines drawn by a GDI+ routine. For example, the DrawPolygon subroutine draws a series of lines, and its Pen parameter determines how all the lines are drawn.

A Brush object determines how areas are filled. A Brush sets the area's fill color, hatch pattern, color gradient, and texture. Chapter 31 provides more advanced details of these and provides figures showing examples. The Brush applies to GDI+ routines that fill closed areas such as FillRectangle, FillEllipse, and FillPolygon.

The basic steps for drawing a simple shape are:

  1. Obtain a Graphics object.

  2. Define a Brush object and fill with it.

  3. Define a Pen object and draw with it.

For example, the Paint event handler shown in the following code runs when the form needs to redraw itself. The Paint event handler's e.Graphics parameter gives the Graphics object on which the program should draw. When the event handler is finished, Visual Basic copies the contents drawn in this Graphics object onto the parts of the form that must be redrawn. The event handler creates an orange SolidBrush object. SolidBrush is a class derived from the Brush class, so it will serve as a Brush. The program uses the brush to fill the circle bounded by the square with upper-left corners at (10, 10) and 100 pixels wide and 100 pixels tall. The code then creates a pen representing a 10-pixel wide blue line and uses it to draw the outline of the same circle. The result is an orange filled circle with a thick blue border.

Private Sub Form1_Paint(ByVal sender As Object,
 ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
    Using circle_brush As New SolidBrush(Color.Orange)
        e.Graphics.FillEllipse(circle_brush, 10, 10, 100, 100)
    End Using

    Using circle_pen As New Pen(Color.Blue, 10)
        e.Graphics.DrawEllipse(circle_pen, 10, 10, 100, 100)
    End Using
End Sub

Whenever the form is hidden and exposed, partially covered and exposed, minimized and restored or maximized, or resized to expose a new part of the form, the Paint event handler executes and redraws the circle.

The Graphics object's filling and drawing methods provide several overloaded versions. Most can take an object parameter that defines the shape to draw. For example, the FillEllipse and DrawEllipse methods can take a Rectangle as a parameter to define the ellipse's bounding rectangle.

This provides a convenient method for ensuring that a filled area and its outline match exactly. The following code draws the same circle as the previous example, but it uses a Rectangle method to define the circle. It uses the same Rectangle for its calls to FillEllipse and DrawEllipse, so it's easy to tell that they define exactly the same circle. If you modify this code to change the circle, you don't need to remember to change its coordinates everywhere they occur, because the circle is defined in only one place (the Rectangle).

Private Sub Form1_Paint(ByVal sender As Object,
 ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
    Dim rect As New Rectangle(10, 10, 100, 100)
    Using circle_brush As New SolidBrush(Color.Orange)
        e.Graphics.FillEllipse(circle_brush, rect)
    End Using

    Using circle_pen As New Pen(Color.Blue, 10)
        e.Graphics.DrawEllipse(circle_pen, rect)
    End Using
End Sub

All GDI+ drawing is based on these simple steps, but there are a lot of variations. Pens and brushes can be much more complicated. For example, you can fill a polygon with a color gradient that follows a path you define and then outline it with a custom dash pattern. The Graphics object also provides some fairly exotic drawing routines such as DrawBezier, which draws a Bézier curve.

The following sections describe the namespaces containing the most useful GDI+ objects. Chapter 31 provides additional details and contains pictures of the results produced by many of these objects.

DRAWING NAMESPACES

Before jumping into GDI+ graphics, it's worth taking a moment to learn which namespaces contain which objects. By default, the System.Drawing namespace is imported into new Windows Forms applications, so you don't need to import it explicitly to work with Graphics, Pen, Brush, and the other basic drawing objects. However, if you want to create custom dash patterns, linear gradient color fills, or advanced image files, you must know which namespaces to import into your application.

System.Drawing

The System.Drawing namespace contains the most important and basic GDI+ classes. These classes include Graphics, Pen, Brush, Font, FontFamily, Bitmap, Icon, and Image. The following table describes the most useful System.Drawing classes.

CLASS

DESCRIPTION

Graphics

This is without doubt the most important class you'll use when creating graphics. An object of this class represents the surface you're going to draw on. That could be a PictureBox, form, bitmap in memory, or whatever. The class provides the methods for drawing lines, rectangles, ellipses, and so forth.

Pen

This class represents the drawing characteristics of a line, including the line's color, thickness, dash style, and so forth.

Pens

This class provides a large number of predefined pens with different colors and width 1. For example, you can use Pens.Blue as a standard blue pen.

Brush

This class represents how solid areas are filled. It determines whether the area is solidly colored, hatched, filled with a pattern, and so on.

Brushes

This class provides a large number of predefined solid brushes with different colors. For example, you can use Brushes.Green to fill an area with green.

SolidBrush

This class represents a solid brush. When you want to fill an object with a solid color, you use a SolidBrush class. This is by far the most common type of fill for most applications.

Bitmap

This class represents a bitmap image defined by pixel data rather than drawn lines.

Icon

This class represents a Windows icon similar to a bitmap.

Metafile

This class represents a graphic metafile that contains graphical operations that a program can record, save to a file, load from a file, and play back later.

Image

This is an abstract base class from which Bitmap, Icon, and Metafile inherit. Some routines can work with any of these kinds of objects, so they take an Image parameter. (In brief, a bitmap is a typical picture. An icon has additional transparency and possibly hot-spot information so it can act as a form or application icon or mouse pointer. A metafile defines a drawing in terms of lines, curves, and filled areas rather than using pixels in a bitmap. This lets the program later redraw the image scaled, rotated, or otherwise transformed smoothly without the distortion that would occur in a bitmapped image.)

Font

This class represents a particular font. It defines the font's name, size, and style (such as italic or bold).

FontFamily

This class represents a group of typefaces with similar characteristics.

Region

This class defines a shape created from rectangles and paths. You can fill a region, use it to perform hit testing, or clip a drawing to a region.

The System.Drawing namespace also defines some structures that a program can use for drawing. The following table describes the most useful of these structures.

STRUCTURE

DESCRIPTION

Color

This object defines a color's red, green, and blue components as values between 0 and 255, plus an alpha value that indicates the color's transparency. An alpha value of 0 means the object is completely transparent, and a value of 255 means it is totally opaque.

Point

This object defines a point's X and Y coordinates.

Size

This object adefines a width and height.

Rectangle

This object defines a rectangle using a Point and a Size.

GDI+ routines work in pixels on the screen, printer, or whatever object they are drawing on, so the Point, Size, and Rectangle structures hold integral coordinates and sizes. However, the System.Drawing namespace also defines PointF, SizeF, and RectangleF classes to work with floating-point values.

The Color class provides a large number of predefined color values. For example, Color.PaleGreen defines a light green color. You can use these predefined colors instead of creating a new color object.

System.Drawing.Drawing2D

The System.Drawing.Drawing2D namespace contains most of the other objects you'll need to draw more advanced two-dimensional graphics. Some of these classes refine the more basic drawing classes, or define values for those classes. For example, the HatchBrush class represents a specialized type of Brush that fills with a hatch pattern. The following table describes this namespace's most useful classes.

CLASS

DESCRIPTION

HatchBrush

This class defines a Brush that fills an area with a hatch pattern. It defines the pattern, a foreground color, and a background color.

LinearGradientBrush

This class defines a Brush that fills an area with a linear color gradient. By default the fill shades smoothly from one color to another along a line that you define, but it can also represent multicolor gradients.

Blend

This class represents a blend pattern for a LinearGradientBrush or PathGradientBrush. For example, suppose that you define a gradient running from red to yellow. Normally the gradient is smooth and linear, but you can use a Blend to change this. For example, you might want the color to change from red to yellow very quickly, so it is 80 percent yellow only 20 percent of the way across the gradient.

PathGradientBrush

This class is similar to a LinearGradientBrush except its gradient follows a path rather than a line.

ColorBlend

This class defines colors and positions for LinearGradientBrush or PathGradientBrush. This lets you make the colors vary between several different colors along the brush's path.

GraphicsPath

This class represents a series of connected lines and curves. You can draw, fill, or clip to a GraphicsPath. For example, you could add text to a GraphicsPath and then draw its outline or clip a drawing so that it only shows within the text's path.

Matrix

This class represents a 3 × 3 transformation matrix. You can use matrixes to translate, scale, and rotate graphics operations. See the section "Transformation Basics" later in this chapter for more information.

Chapter 31 has more to say about the gradient brushes. It includes sample images.

The System.Drawing.Drawing2D namespace also defines some enumerations that are useful for more advanced drawing. The following table describes the most useful of these enumerations.

ENUMERATION

DESCRIPTION

DashCap

These values determine how the ends of a dash in a dashed line are drawn. DashCap values include Flat, Round, and Triangle. These give the same appearance as the Flat, Round, and Triangle LineCap enumerations shown in Figure 30-1.

The LineCap enumeration determines how a line's end point is drawn.

Figure 30.1. The LineCap enumeration determines how a line's end point is drawn.

DashStyle

These values determine how a dashed line is drawn. DashStyle values include Dash, DashDot, DashDotDot, Dot, Solid, and Custom. If you set a Pen's DashStyle property to DashStyle.Custom, you should also set its DashPattern property to an array telling the Pen how many to pixels to draw and skip. For example, the array {10, 20, 5, 2} means draw 10, skip 2, draw 5, skip 2, and then repeat as necessary.

LineCap

These values determine how the ends of a line are drawn. Values include ArrowAnchor, DiamondAnchor, Flat, NoAnchor, Round, RoundAnchor, Square, SquareAnchor, Triangle, and Custom. If LineCap is Custom, you should use a CustomLineCap object to define the cap. Figure 30-1 shows the standard LineCaps.

LineJoin

These values determine how lines are joined by a GDI+ method that draws connected lines. For example, the DrawPolygon and DrawLines methods use this property. Figure 30-2 shows the possible values Bevel, Miter, Round, and MiterClipped. MiterClipped produces either a mitered or beveled corner, depending on whether the miter's length exceeds a certain limit determined by the Pen class's MiterLimit property. The MiterClipped example in Figure 30-2 shows one join that exceeds this limit and one that does not.

The LineJoin enumeration determines how lines are joined

Figure 30.2. The LineJoin enumeration determines how lines are joined

HatchStyle

These values define the hatch style used by a HatchBrush object to fill an area. This enumeration includes 54 values, so they are not all listed here. Example program HatchStyles, which is available for download on the book's web site and shown in Figure 30-3, lists them and shows samples.

The HatchStyle enumeration determines how HatchBrush objects fill areas.

Figure 30.3. The HatchStyle enumeration determines how HatchBrush objects fill areas.

System.Drawing.Imaging

The System.Drawing.Imaging namespace contains classes that deal with more advanced bitmap graphics. It includes classes that define image file formats such as GIF and JPG, classes that manage color palettes, and classes that define metafiles. The following table describes this namespace's most useful classes.

CLASS

DESCRIPTION

ImageFormat

This class specifies an image's format. This can be one of BMP, EMF, EXIF, GIF, ICON, JPEG, memory bitmap, PNG, TIFF, or WMF. For descriptions of these image format types, try searching for them at web sites such as Webopedia (www.webopedia.com). The page www.webopedia.com/DidYouKnow/Internet/2002/JPG_GIF_PNG.asp discusses the differences between the three most common web image formats: GIF, JPEG, and PNG. The Microsoft web site has a comparison of the BMP, GIF, JPEG, Exif, PNG, and TIFF formats at msdn.microsoft.com/ms536393.aspx.

ColorMap

This class defines a mapping from old color values to new ones. You can use the ColorMap class to change some colors in an image to others.

ColorPalette

This class represents a palette of color values. (A palette is a collection of color values that are used in a particular image. For example, 8-bit color images can contain only 256 different colors. The image's color palette lists the colors used, and an 8-bit numeric value gives each pixel's color index in the palette. Recently, higher color models such as 16-, 24-, and 32-bit color have become more common. In those color models, the bits give each pixel's red, green, and blue color components directly rather than referring to a color palette, so no palette is needed.)

Metafile

This class represents a graphic metafile that contains drawing instructions. You can create, save, reload, and play back metafile information.

MetafileHeader

This class defines the attributes of a Metafile object.

MetaHeader

This class contains information about a Windows metafile (WMF).

WmfPlaceableFileHeader

This class defines how a metafile should be mapped to an output device. You can use this to ensure that the metafile is properly sized when you import it into a drawing program such as CorelDRAW.

System.Drawing.Text

The System.Drawing.Text namespace contains only three classes. These three classes provide a somewhat awkward method for learning about the fonts installed on the system or the fonts installed for an application. The following table describes these three classes.

CLASS

DESCRIPTION

FontCollection

A base class for the derived InstalledFontCollection and PrivateFontCollection classes. It provides a method that returns an array of FontFamily objects.

InstalledFontCollection

This is derived from the FontCollection class. This class's Families method returns an array containing FontFamily objects representing the fonts installed on the system.

PrivateFontCollection

This is also derived from the FontCollection class. Objects from this class represent fonts installed by an application from font files. The program can use this object to install fonts just for the use of the application and not for the rest of the system. This class provides methods for installing and listing the application's fonts.

It is rather odd that this class is defined just to provide one method that returns an array of FontFamily objects. It would have made more sense to give the FontCollection class a method such as ListInstalledFonts or to give the InstalledFontCollection class a shared method that creates such a FontCollection object. That's not the way these classes work, however.

Example program ListInstalledFonts uses the following code to list the system's installed fonts. The program creates an instance of the InstalledFontCollection and uses that object's Families method to get an array of the installed FontFamily objects. The program then loops through the array to list the fonts.

Private Sub Form1_Load(ByVal sender As System.Object,
 ByVal e As System.EventArgs) Handles MyBase.Load
    ' Get the installed fonts collection.
    Dim installed_fonts As New InstalledFontCollection

    ' Get an array of the system's font families.
    Dim font_families() As FontFamily = installed_fonts.Families()

    ' Display the font families.
    For Each font_family As FontFamily In font_families
        lstFonts.Items.Add(font_family.Name)
    Next font_family
    lstFonts.SelectedIndex = 0
End Sub
                                                  
System.Drawing.Text

The System.Drawing.Text namespace also defines the TextRenderingHint enumeration. Anti-aliasing is a process that uses pixels of different shades to make jagged edges and curves appear smoother. You can set a Graphics object's TextRenderingHint property to tell Visual Basic whether it should use anti-aliasing to smooth the text.

The following table describes the TextRenderingHint enumeration values.

ENUMERATION VALUE

DESCRIPTION

AntiAlias

Characters are drawn anti-aliased without hinting.

AntiAliasGridFit

Characters are drawn anti-aliased with hinting to improve stems and curves.

ClearTypeGridFit

Characters are drawn using ClearType glyphs with hinting. This takes advantage of ClearType font features. (In this context, a glyph is the image of a letter. Some fonts are drawn as glyphs and others such as TrueType fonts are drawn as outlines. TrueType was developed jointly by Microsoft and Apple. ClearType is a newer type of glyph font developed by Microsoft.)

SingleBitPerPixel

Characters are drawn without anti-aliasing or hinting. This is the fastest and lowest-quality setting.

SingleBitPerPixelGridFit

Characters are drawn without anti-aliasing, but with hinting.

SystemDefault

Characters are drawn using the system default setting.

The following code shows how a program can display text with and without anti-aliasing. First it creates a large font. It sets the Graphics object's TextRenderingHint property to AntiAliasGridFit and draws some text. Then it sets TextRenderingHint to SingleBitPerPixel and draws the text again.

Private Sub Form1_Paint(ByVal sender As Object,
 ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
    ' Make a big font.
    Using the_font As New Font(Me.Font.FontFamily,
     40, FontStyle.Bold, GraphicsUnit.Pixel)

        ' Draw without anti-aliasing.
        e.Graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit
        e.Graphics.DrawString("Alias", the_font, Brushes.Black, 5, 5)
' Draw with anti-aliasing.
        e.Graphics.TextRenderingHint = TextRenderingHint.SingleBitPerPixel
        e.Graphics.DrawString("Alias", the_font, Brushes.Black, 5, 50)
    End Using
End Sub
                                                 
HELPFUL HINTS

Figure 30-4 shows the result, greatly enlarged to emphasize the difference. Notice how the anti-aliased version uses different shades of gray to make the text appear smoother.

Anti-aliasing (top) makes characters appear smoother.

Figure 30.4. Anti-aliasing (top) makes characters appear smoother.

For information on anti-aliasing as it applies to shapes other than text, see the section "Anti-Aliasing" later in this chapter.

System.Drawing.Printing

The System.Drawing.Printing namespace contains objects for printing and managing the printer's characteristics.

Normally, to generate a printed document, you create a PrintDocument object. You set the object's properties to define printing attributes and then call its Print method. As it prints, the PrintDocument object generates PrintPage events that let you draw on the printout's pages.

Other classes in this namespace define properties for the PrintDocument object. The following table describes the most useful of these property objects.

CLASS

DESCRIPTION

PageSettings

This class defines the page settings for either an entire PrintDocument or for a particular page. This object has properties that are Margins, PaperSize, PaperSource, PrinterResolution, and PrinterSettings objects.

Margins

This class defines the margins for the printed page through its Top, Bottom, Left, and Right properties.

PaperSize

This class defines the paper's size. You can set the object's Kind property to a standard value such as A2, Legal, or Letter. Alternatively, you can set the object's Height and Width properties explicitly.

PaperSource

This class defines the printer's paper source. You can set this object's Kind property to such values as AutomaticFeed, Upper, Middle, Lower, Envelope, and ManualFeed.

PrinterResolution

This class defines the printer's resolution.

PrinterSettings

This class defines the printer's settings. You can use this class to get setting values such as whether the printer can print double-sided (CanDuplex), the names of the installed printers (InstalledPrinters), and the printer's supported resolutions (PrinterResolutions). You can use other properties to control the printer. For example, you can set the number of copies (Copies), set the minimum and maximum page number the user can select in a print dialog (MinimumPage and MaximumPage), and determine whether the printer collates its output (Collate).

GRAPHICS

Whenever you draw in Visual Basic .NET, you need a Graphics object. A Graphics object represents a drawing surface, whether it is a Form, PictureBox, Bitmap in memory, metafile, or printer surface.

The Graphics class provides many methods for drawing shapes and filling areas. It also includes properties and methods that modify the graphics results. For example, its transformation methods enable you to scale, translate, and rotate the drawing output.

The following sections describe the Graphics object's properties and methods for drawing, filling, and otherwise modifying the drawing.

Drawing Methods

The Graphics object provides many methods for drawing lines, rectangles, curves, and other shapes. The following table describes these methods.

METHOD

DESCRIPTION

DrawArc

Draws an arc of an ellipse.

DrawBezier

Draws a Bézier curve. See the section "DrawBezier" later in this chapter for an example.

DrawBeziers

Draws a series of Bézier curves. See the section "DrawBezier" later in this chapter for an example.

DrawClosedCurve

Draws a closed curve that joins a series of points, connecting the final point to the first point. See the section "DrawClosedCurve" later in this chapter for an example.

DrawCurve

Draws a smooth curve that joins a series of points. This is similar to a DrawClosedCurve, except that it doesn't connect the final point to the first point.

DrawEllipse

Draws an ellipse. To draw a circle, draw an ellipse with a width equal to its height.

DrawIcon

Draws an Icon onto the Graphics object's drawing surface.

DrawIconUnstretched

Draws an Icon object onto the Graphics object's drawing surface without scaling. If you know that you will not resize the icon, this may be faster than the DrawIcon method.

DrawImage

Draws an Image object onto the Graphics object's drawing surface. Note that Bitmap is a subclass of Image, so you can use this method to draw a Bitmap on the surface.

DrawImageUnscaled

Draws an Image object onto the drawing surface without scaling. If you know that you will not resize the image, this may be faster than the DrawImage method.

DrawLine

Draws a line.

DrawLines

Draws a series of connected lines. If you need to draw a series of connected lines, this is much faster than using DrawLine repeatedly.

DrawPath

Draws a GraphicsPath object. See the section "DrawPath" later in this chapter for an example.

DrawPie

Draws a pie slice taken from an ellipse.

DrawPolygon

Draws a polygon. This is similar to DrawLines, except that it connects the last point to the first point.

DrawRectangle

Draws a rectangle.

DrawRectangles

Draws a series of rectangles. If you need to draw a series of rectangles, this is much faster than using DrawRectangle repeatedly.

DrawString

Draws text on the drawing surface.

The following sections provide examples of some of the more complicated of these drawing methods.

DrawBezier

The DrawBezier method draws a Bézier curve. A Bézier curve is a smooth curve defined by four control points. The curve starts at the first point and ends at the last point. The line between the first and second points gives the curve's initial direction. The line connecting the third and fourth points gives its final direction as it enters the final point.

The following code draws a Bézier curve. It starts by defining the curve's control points. It then draws dashed lines connecting the points, so you can see where the control points are in the final drawing. You would omit this step if you just wanted to draw the curve. Next the program sets the Graphics object's SmoothingMode property to HighQuality, so the program draws a smooth, anti-aliased curve. The SmoothingMode property is described in the section "SmoothingMode" later in this chapter. The program creates a black pen three pixels wide and draws the Bézier curve.

Private Sub Form1_Paint(ByVal sender As Object,
 ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
    ' Define the Bezier curve's control points.
    Dim pts() As Point = {
        New Point(10, 10),
        New Point(200, 10),
        New Point(50, 200),
        New Point(200, 150)
    }
    ' Connect the points with dashed lines.
    Using dashed_pen As New Pen(Color.Black, 0)
        dashed_pen.DashStyle = Drawing2D.DashStyle.Dash
        For i As Integer = 0 To 2
            e.Graphics.DrawLine(dashed_pen, pts(i), pts(i + 1))
        Next i
    End Using

    ' Draw the Bezier curve.
    e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
    Using bez_pen As New Pen(Color.Black, 3)
        e.Graphics.DrawBezier(bez_pen, pts(0), pts(1), pts(2), pts(3))
    End Using
End Sub
                                                  
DrawBezier

Figure 30-5 shows the result. You can see in the picture how the control points determine the curve's end points and the direction it takes at them.

The DrawBezier method draws a smooth curve deflned by four control points.

Figure 30.5. The DrawBezier method draws a smooth curve deflned by four control points.

DrawBeziers

The DrawBeziers method draws a series of Bézier curves with common end points. It takes as parameters an array of points that determine the curves' end points and interior control points. The first four entries in the array represent the first curve's starting point, its two interior control points, and the curve's end point. The next curve uses the first curve's end point as its starting point, provides two interior control points, and its own end point. This pattern repeats for each of the curves. To draw N curves, the array should contain 3 * N + 1 points.

Figure 30-6 shows two Bézier curves drawn by the DrawBeziers method. Notice that the two curves share a common end point, but they do not meet smoothly. To make them meet smoothly, you would need to ensure that the last two points in the first curve and the first two points in the second curve (one of which is the same as the last point in the first curve) all lie along the same line.

The DrawBeziers method draws a series of Bézier curves with common end points.

Figure 30.6. The DrawBeziers method draws a series of Bézier curves with common end points.

DrawClosedCurve

Using the DrawBeziers method, you can draw a series of connected curves, but joining them smoothly is difficult. The DrawClosedCurve method connects a series of points with a smooth curve.

DrawClosedCurve takes as parameters a pen and an array of points to connect. Figure 30-7 shows an example. Notice that the curve is smooth and that it passes through each of the control points exactly. If you just want to connect a series of points smoothly, it is easier to use a closed curve than Bézier curves.

The DrawClosedCurve method draws a smooth curve connecting a series of points.

Figure 30.7. The DrawClosedCurve method draws a smooth curve connecting a series of points.

The DrawCurve method is similar to DrawClosedCurve, except that it doesn't connect the last point to the first.

Overloaded versions of the DrawClosedCurve method take a tension parameter that indicates how tightly the curve bends. Usually this value is between 0 and 1. The value 0 makes the method connect the curve's points with straight lines. The value 1 draws a nicely rounded curve. Tension values greater than 1 produce some strange (but sometimes interesting) results.

Figure 30-8 shows closed curves drawn with tension set to 0.0, 0.25, 0.5, 0.75, and 1.0. It uses progressively thicker lines so you can see which curves are which. You can see the curves growing smoother as the tension parameter increases and the pen thickens.

The DrawClosedCurve method's tension parameter determines how tightly the curve bends.

Figure 30.8. The DrawClosedCurve method's tension parameter determines how tightly the curve bends.

Overloaded versions of the DrawCurve method also take tension parameters.

DrawPath

The DrawPath method draws a GraphicsPath object as shown in the following code. The program creates a new, empty GraphicsPath object and uses its AddString method to add a string to the path. This method takes as parameters a string, FontFamily, font style, font size, point where the text should start, and a string format. The code sets the Graphics object's SmoothingMode property to draw anti-aliased curves. It then calls the FillPath method to fill the area defined by the GraphicsPath object with white and uses the DrawPath method to draw the path's outline in black.

Private Sub Form1_Paint(ByVal sender As Object,
 ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
    ' Create a GraphicsPath.
    Using graphics_path As New Drawing2D.GraphicsPath
        ' Add some text to the path.
        graphics_path.AddString("GraphicsPath",
            New FontFamily("Times New Roman"),
            CInt(FontStyle.Bold),
            80, New Point(10, 10),
            StringFormat.GenericTypographic)

        ' Draw the path.
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias
        e.Graphics.FillPath(Brushes.White, graphics_path)
        Using thick_pen As New Pen(Color.Black, 3)
            e.Graphics.DrawPath(thick_pen, graphics_path)
        End Using
    End Using
End Sub
                                                  
DrawPath

Figure 30-9 shows the result.

The DrawPath method draws the outline defi ned by a GraphicsPath object.

Figure 30.9. The DrawPath method draws the outline defi ned by a GraphicsPath object.

You can use GraphicsPath objects to make all sorts of interesting effects. For example, you could fill a GraphicsPath with a color gradient, hatch pattern, or bitmap.

Flling Methods

The Graphics object provides many methods for filling areas. These correspond exactly to the drawing methods that define a closed shape. For example, DrawRectangle draws a rectangle and FillRectangle fills one.

Corresponding draw and fill methods take exactly the same parameters, except that the filling methods use a Brush object instead of a Pen object. For example, the following statements fill and draw a GraphicsPath object. The only difference in the parameters is the Pen or Brush.

e.Graphics.FillPath(Brushes.White, graphics_path)
e.Graphics.DrawPath(Pens.Black, graphics_path)

The Graphics object provides the following methods for filling areas: FillClosedCurve, FillEllipse, FillPath, FillPie, FillPolygon, FillRectangle, and FillRectangles. These methods work the same way as the corresponding drawing methods (DrawClosedCurve, DrawEllipse, DrawPath, DrawPie, DrawPolygon, DrawRectangle, and DrawRectangles), except they fill an area with a brush rather than drawing it with a pen. See the section "Drawing Methods" earlier in this chapter for descriptions of these methods.

Other Graphics Properties and Methods

The following table describes the Graphics object's most useful properties and methods, other than those that draw or fill shapes.

PROPERTIES/METHODS

DESCRIPTION

AddMetafileComment

If the Graphics object is attached to a metafile, this adds a comment to it. Later, if an application enumerates the metafile's records, it can view the comments.

Clear

Clears the Graphics object and fills it with a specific color. For example, a form's Paint event handler might use the statement e.Graphics.Clear(Me.BackColor) to clear the form using its background color.

Clip

Determines the Region object used to clip a drawing on the Graphics surface. Any drawing command that falls outside of this Region is clipped off and not shown in the output.

Dispose

Releases the resources held by the Graphics object. You can use this method to free the resources of an object that you no longer need sooner than they would be freed by garbage collection. For a more detailed discussion, see the section "Dispose" in Chapter 26, "Classes and Structures."

DpiX

Returns the horizontal number of dots per inch (DPI) for this Graphics object's surface.

DpiY

Returns the vertical number of DPI for this Graphics object's surface.

EnumerateMetafile

If the Graphics object is attached to a metafile, this sends the metafile's records to a specified callback subroutine one at a time.

ExcludeClip

Updates the Graphics object's clipping region to exclude the area defined by a Region or Rectangle.

FromHdc

Creates a new Graphics object from a handle to a device context (DC). (A device context is a structure that defines an object's graphic attributes: pen, color, fill, and so forth.)

FromHwnd

Creates a new Graphics object from a window handle (hWnd).

FromImage

Creates a new Graphics object from an Image object. This is a very common way to make a new Graphics object to manipulate a bitmap.

InterpolationMode

Determines whether drawing routines use anti-aliasing when drawing images. See the section "InterpolationMode" later in this chapter for an example.

IntersectClip

Updates the Graphics object's clipping region to be the intersection of the current clipping region and the area defined by a Region or Rectangle. (The clipping region determines where GDI+ will draw output. If a line falls outside of the clipping region, GDI+ doesn't draw the part that sticks out. Normally, an image's clipping region includes its whole visible area, but you can redefine it so that, for example, parts of the visible area are not drawn.)

IsVisible

Returns True if a specified point is within the Graphics object's visible clipping region.

MeasureCharacterRanges

Returns an array of Region objects that show where each character in a string will be drawn.

MeasureString

Returns a SizeF structure that gives the size of a string drawn on the Graphics object with a particular font.

MultiplyTransform

Multiplies the Graphics object's current transformation matrix by another transformation matrix.

PageScale

Determines the amount by which drawing commands are scaled. For example, if you set this to 2, every coordinate and measurement is scaled by a factor of 2 from the origin.

PageUnits

Determines the units of measurement. This can be Display (1/75 inch), Document (1/300 inch), Inch, Millimeter, Pixel, or Point (1/72 inch).

RenderingOrigin

Determines the point used as a reference when hatching. Normally this is (0, 0), so all HatchBrushes use the same RenderingOrigin and, if you draw two overlapping hatched areas, their hatch patterns line up. If you change this property, you can make their hatch patterns not line up.

ResetClip

Resets the object's clipping region, so the drawing is not clipped.

ResetTransformation

Resets the object's transformation matrix to the identity matrix, so the drawing is not transformed.

Restore

Restores the Graphics object to a state saved by the Save method. See the section "Saving and Restoring Graphics State" later in this chapter for an example.

RotateTransform

Adds a rotation to the object's current transformation. This rotates all drawing by a specified amount. See the section "Transformation Basics" later in this chapter for an example.

Save

Saves the object's current state in a GraphicsState object, so you can later restore it by calling the Restore method. See the section "Saving and Restoring Graphics State" later in this chapter for an example.

ScaleTransform

Adds a scaling transformation to the Graphics object's current transformation. This scales all drawing by a specified factor in the X and Y directions. See the section "Transformation Basics" later in this chapter for an example.

SetClip

Sets or merges the Graphics object's clipping area to another Graphics object, a GraphicsPath object, or a Rectangle. Only parts of drawing commands that lie within the clipping region are displayed.

SmoothingMode

Determines whether drawing routines use anti-aliasing when drawing lines and curves. See the section "SmoothingMode" later in this chapter for an example.

TextRenderingHint

Determines whether text is drawn with anti-aliasing and hinting. See the section "TextRenderingHint" later in this chapter and the section "System.Drawing.Text" earlier in this chapter for more details.

Transform

Gets or sets the Graphics object's transformation matrix. This matrix represents all scaling, translation, and rotation applied to the object.

TransformPoints

Applies the object's current transformation to an array of points.

TranslateTransform

Adds a translation transformation to the Graphics object's current transformation. This offsets all drawing a specified distance in the X and Y directions. See the section "Transformation Basics" later in this chapter for an example.

The following sections give examples of some of the more important (but confusing) Graphics properties and methods.

Anti-Aliasing

Aliasing is an effect caused when you draw lines, curves, and text that do not line up exactly with the screen's pixels. For example, if you draw a vertical line, it neatly fills in a column of pixels. If you draw a line at a 45-degree angle, it also fills a series of pixels that are nicely lined up, but the pixels are a bit farther apart and that makes the line appear lighter on the screen. If you draw a line at some other angle (for example, 30 degrees), the line does not line up exactly with the pixels. The line will contain some runs of two or three pixels in a horizontal group. The result is a line that is lighter than the vertical line and that is noticeably jagged.

A similar effect occurs when you resize a bitmap or other image. If you enlarge an image by simply drawing each pixel as a larger block of the same color, the result is blocky. If you shrink an image by removing some pixels, the result may have tears and gaps.

Anti-aliasing is a process that smoothes out lines, text, and images. Instead of drawing a series of pixels that all have the same color, the drawing routines give pixels different shades of color to make the result smoother.

The Graphics object provides three properties that control anti-aliasing for lines and curves, text, and images: SmoothingMode, TextRenderingHint, and InterpolationMode.

SmoothingMode

The SmoothingMode property controls anti-aliasing for drawn lines and curves, and for filled shapes. This property can take the values AntiAlias, Default, HighQuality, HighSpeed, and None. The following code shows how a program might draw a circle's outline and a filled circle using the HighQuality SmoothingMode:

gr.SmoothingMode = SmoothingMode.HighQuality
gr.DrawEllipse(Pens.Black, 10, 10, 20, 20)
gr.FillEllipse(Brushes.Black, 30, 10, 20, 20)

It's hard to see any difference in a book, although the difference is clear on the screen. It's not clear whether there's any difference between the Default, HighSpeed, and None modes, or between HighQuality and AntiAlias, at least on this computer. The HighQuality and AntiAlias modes are noticeably smoother than the others, however.

TextRenderingHint

The TextRenderingHint property controls anti-aliasing for text. This property can take the values AntiAlias, AntiAliasGridFit, ClearTypeGridFit, SingleBitPerPixel, SingleBitPerPixelGridFit, and SystemDefault. The following code shows how a program can draw text using the TextRenderingHint value AntiAliasGridFit:

gr.TextRenderingHint = TextRenderingHint.AntiAliasGridFit
gr.DrawString("TextRenderingHint", Me.Font, Brushes.Black, 10, 10)

As is the case with SmoothingMode values, it's hard to see any difference in a book, but the difference is clear on the screen. The TextRenderingHint value SingleBitPerPixel produces a poor result. SystemDefault and SingleBitPerPixelGridFit give an acceptable result (the default appears to be AntiAliasGridFit on this computer). AntiAlias and AntiAliasGridFit give the best results.

Most of the differences are quite subtle. The "grid fit" versions use hinting about where the characters are positioned to try to improve stems and curves. If you look extremely closely (perhaps with a magnifying glass), you can see that the base of a T is a bit cleaner and more solid in the AntiAliasGridFit and ClearTypeGridFit modes than in the AntiAlias mode. The "grid fit" modes also provide text that is slightly more compact horizontally than the AntiAlias mode.

InterpolationMode

The InterpolationMode property controls anti-aliasing when a program shrinks or enlarges an image. This property can take the values Bicubic, Bilinear, Default, High, HighQualityBicubic, HighQualityBilinear, Low, and NearestNeighbor.

Example program InterpolationModes, which is available for download on the book's web site, draws a smiley face in a bitmap. It then draws the bitmap's image at enlarged and reduced scales using InterpolationMode values. The differences are hard to see in a book but are obvious on the screen.

For enlarged images, the NearestNeighbor interpolation mode gives a sharply blocky result. All of the other modes give a slightly fuzzier image that looks a little smoother but that is still somewhat blocky.

For reduced images, the three high-quality modes (High, HighQualityBilinear, and HighQualityBicubic) produce very nice results. The other modes cause gaps in the lines in the reduced images.

All of the interpolation modes produce better results on photographic images instead of the line drawings used in this example.

Speed Considerations

The anti-aliasing settings for all three of these properties provide smoother results, but they are slower. For a few lines, strings, and images, the difference in performance won't be an issue. However, if you build a more intensive application (such as a mapping program that draws several thousand street segments on the form), you may notice a difference in speed. In that case, you may be willing to accept slightly worse appearance in exchange for better performance.

Transformation Basics

Graphical transformations modify the coordinates and sizes you use to draw graphics to produce a result that is scaled, translated, or rotated. For example, you could apply a scaling transformation that multiplies by 2 in the X direction and 3 in the Y direction. If you then drew a line between the points (10, 10) and (20, 20), the result drawn on the screen would connect the points (10 * 2, 10 * 3) = (20, 30) and (20 * 2, 20 * 3) = (40, 60). This stretches the line so it is larger overall, but it stretches its height more than its width (a factor of 3 versus a factor of 2). Notice that this also moves the line farther from the origin [from (10, 10) to (20, 30)]. In general, a scaling transformation moves an object farther from the origin unless it lies on the origin.

You don't really need to understand all the details of the mathematics of transformations to use them, but a little background is quite helpful.

Transformation Mathematics

In two dimensions, you can represent scaling, translation, and rotation with 3 × 3 matrixes. You represent a point with a vector of three entries, two for the X and Y coordinates and a final 1 that gives the vector three entries so that it matches the matrices.

When you multiply a point's coordinates by a transformation matrix, the result is the transformed point.

To multiply a point by a matrix, you multiply the point's coordinates by the corresponding entries in the matrix's columns. The first transformed coordinate is the point's coordinates times the first column, the second transformed coordinate is the point's coordinates times the second column, and the third transformed coordinate is the point's coordinates times the third column.

The following calculation shows the result when you multiply a generic vector <A, B, C> by a matrix. When you work with two-dimensional transformations, the value C is always 1.

Transformation Mathematics

The following matrix represents scaling by a factor of Sx in the X direction and a factor of Sy in the Y direction:

Transformation Mathematics

The following example shows the point (10, 20) multiplied by a matrix that represents scaling by a factor of 2 in the X direction and 3 in the Y direction. The result is the vector <20, 60, 1>, which represents the point (20, 60) as you should expect.

Transformation Mathematics

The following matrix represents translation through the distance Tx in the X direction and Ty in the Y direction:

Transformation Mathematics

The following matrix represents rotation through the angle t:

Transformation Mathematics

Finally, the following transformation, called the identity transformation, leaves the point unchanged. If you multiply a point by this matrix, the result is the same as the original point.

Transformation Mathematics

You can work through some examples to verify that these matrices represent translation, scaling, rotation, and the identity, or consult an advanced graphics programming book for proofs.

One of the most useful and remarkable properties of matrix/point multiplication is that it is associative. If p is a point and T1 and T2 are transformation matrices, p * T1 * T2 = (p * T1) * T2 = p * (T1 * T2).

This result means that you can multiply any number of transformation matrices together to create a single combined matrix that represents all of the transformations applied one after the other. You can then apply this single matrix to all the points that you need to draw. This can save a considerable amount of time over multiplying each point by a long series of matrices one at a time.

Transformation Code

The Graphics object maintains a current transformation matrix at all times, and it provides several methods that let you add more transformations to that matrix. The ScaleTransform, TranslateTransform, and RotateTransform methods add a new transformation to the current transformation. These methods take parameters that specify the amount by which points should be scaled, translated, or rotated.

A final parameter indicates whether you want to prepend the new transformation on the left (MatrixOrder.Prepend) or append it on the right (MatrixOrder.Append) of the current transformation. If you prepend the new transformation on the left, that transformation is applied before any that are already part of the current transformation. If you append the new transformation on the right, that transformation is applied after any that are already part of the current transformation.

Strangely, the default if you omit this parameter is to prepend the new transformation on the left. That means transformations that you add last are applied first. That, in turn, means that you must compose a combined transformation backward. If you want to rotate, then scale, then translate, you need to prepend the translation first, the scaling second, and the rotation last. That seems very counterintuitive.

A more natural approach is to explicitly set this final parameter to MatrixOrder.Append so that later transformations are applied after existing ones.

The following code shows how a program can use transformations to draw a complex result with a simple drawing routine. Subroutine DrawArrow draws an arrow within the rectangle 0 <= X <= 4, 0 <= Y <= 4. If you were to call this routine without any transformations, you would see a tiny arrow four pixels long and four pixels wide drawn in the upper-left corner of the form.

' Draw an arrow outline.
Private Sub DrawArrow(ByVal gr As Graphics, ByVal hatch_style As HatchStyle)
    Dim pts() As Point = {
        New Point(0, 1),
        New Point(2, 1),
        New Point(2, 0),
        New Point(4, 2),
        New Point(2, 4),
        New Point(2, 3),
        New Point(0, 3)
    }
    Using hatch_brush As New HatchBrush(hatch_style, Color.Black, Color.White)
        gr.FillPolygon(hatch_brush, pts)
    End Using

    Using black_pen As New Pen(Color.Black, 0)
        gr.DrawPolygon(black_pen, pts)
    End Using
End Sub

Private Sub Form1_Paint(ByVal sender As Object,
 ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
    ' Scale by a factor of 30.
e.Graphics.ScaleTransform(30, 30, MatrixOrder.Append)
    DrawArrow(e.Graphics, HatchStyle.Horizontal)

    ' Translate 150 horizontally and 60 vertically.
    e.Graphics.TranslateTransform(150, 60, MatrixOrder.Append)
    DrawArrow(e.Graphics, HatchStyle.Vertical)

    ' Rotate 30 degrees.
    e.Graphics.RotateTransform(30, MatrixOrder.Append)
    DrawArrow(e.Graphics, HatchStyle.Cross)
End Sub
                                                  
Transformation Code

This form's Paint event handler uses the ScaleTransform method to give the Graphics object a transformation that scales by a factor of 30 in both the X and Y directions. It then calls the DrawArrow routine, passing it the parameter HatchStyle.Horizontal. The result is an arrow near the upper-left corner but 30 times larger than the original arrow and filled with a horizontal hatch pattern.

Next the program uses the TranslateTransform method to add a transformation that translates 150 pixels in the X direction and 60 pixels vertically. It appends this transformation so that the drawing is first scaled (the scaling transformation is still part of the Graphics object's current transformation) and then translated. It calls DrawArrow again, passing it the parameter HatchStyle.Vertical, so the result is an arrow 30 times larger than the original, moved 150 pixels to the right and 60 pixels down, and filled with a vertical hatch pattern.

Finally, the program uses the RotateTransform method to add a transformation that rotates the drawing by 30 degrees clockwise around the origin in the upper-left corner. It again appends the transformation so that the drawing is first scaled, then translated, and then rotated. It calls DrawArrow, passing it the parameter HatchStyle.Cross, so the result is an arrow 30 times larger than the original, moved 150 pixels to the right and 60 pixels down, rotated 30 degrees, and filled with a crosshatch pattern.

Figure 30-10 shows the result.

The Graphics object's methods for working with transformations include MultiplyTransform, PageScale, PageUnits, ResetTransformation, RotateTransform, ScaleTransform, Transform, TransformPoints, and TranslateTransform. See the section "Other Graphics Properties and Methods" earlier in this chapter for descriptions of those methods.

This program draws arrows scaled, translated, and rotated.

Figure 30.10. This program draws arrows scaled, translated, and rotated.

Note also that the transformations apply to text as well as drawn lines and filled shapes so a program can easily draw text that is stretched, scaled, and rotated.

Advanced Transformations

You can build very complex transformations by combining simple ones. For example, you can scale around an arbitrary point by combining simple translation and scaling transformations.

A normal scaling transformation moves an object farther away from the origin. If you scale the point (10, 20) by a factor of 20 in the X and Y directions, you get the point (200, 400), which is much farther from the origin. Similarly, if you scale all the points in a shape, all the points move farther from the origin.

To scale the object around some point other than the origin, first translate it so the point of rotation is centered at the origin. Then scale it and translate it back to its original position.

The following code scales a diamond around its center. The DrawDiamond subroutine draws a diamond centered at the point (125, 125). The form's Paint event handler calls DrawDiamond to draw the original diamond. It then translates to move the diamond's center to the origin, scales by a factor of 2 vertically and horizontally, and then reverses the first translation to move the origin back to the diamond's original center.

Private Sub DrawDiamond(ByVal gr As Graphics)
    Dim pts() As Point = {
        New Point(75, 125),
        New Point(125, 75),
        New Point(175, 125),
        New Point(125, 175)
    }
    gr.DrawPolygon(Pens.Black, pts)
End Sub

Private Sub Form1_Paint(ByVal sender As Object,
 ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
    ' Draw the original diamond.
    DrawDiamond(e.Graphics)

    ' Translate to center at the origin.
    e.Graphics.TranslateTransform(−125, −125, MatrixOrder.Append)

    ' Scale by a factor of 2.
    e.Graphics.ScaleTransform(2, 2, MatrixOrder.Append)

    ' Translate the center back to where it was.
    e.Graphics.TranslateTransform(125, 125, MatrixOrder.Append)

    ' Draw the diamond.
    DrawDiamond(e.Graphics)
End Sub
                                                  
Advanced Transformations

Notice that line widths are also scaled by scaling transformations, so in this example, the enlarged diamond's borders are two pixels wide. The Pen object's Width property is a Single, so you can use fractional pen widths if necessary. For example, if you want to scale an object by a factor of 4 and you want the result to have a line width of 2, you can set the Pen's Width to 0.5 and let it scale.

If you want the final result to have a line width of 1, you can also set the Pen's Width to 0. This value tells the GDI+ routines to use a single pixel line width, no matter how the drawing is scaled.

Note also that the line width will not go below 1 if you use a scaling transformation to reduce the size of the drawing.

A particularly useful transformation maps a specific rectangle in world coordinates (the coordinates in which you draw) to a specific rectangle in device coordinates (the coordinates on the drawing surface). For example, you might graph a function in the world coordinates −1 <= X <= 1, −1 <= Y <= 1 and want to map it to the drawing surface rectangle 10 <= X <= 200, 10 <= Y <= 200.

To do this, you can first translate to center the world coordinate rectangle at the origin, scale to resize the rectangle to match the size of the device coordinate rectangle, and then translate to move the origin to the center of the device coordinate rectangle.

The MapRectangles subroutine shown in the following code maps a world coordinate rectangle into a device coordinate rectangle for a Graphics object. It begins by resetting the Graphics object's transformation to clear out anything that may already be in there. Next, the routine translates the center of the world coordinate rectangle to the origin, scales to stretch the world coordinate rectangle to the device coordinate rectangle's size, and then translates to move the origin to the center of the device-coordinate rectangle.

' Map a world coordinate rectangle to a device coordinate rectangle.
Private Sub MapRectangles(ByVal gr As Graphics,
 ByVal world_rect As Rectangle, ByVal device_rect As Rectangle)
    ' Reset the transformation.
    gr.ResetTransform()

    ' Translate to center the world coordinate
    ' rectangle at the origin.
    gr.TranslateTransform(
        CSng(-(world_rect.X + world_rect.Width / 2)),
        CSng(-(world_rect.Y + world_rect.Height / 2)),
        MatrixOrder.Append)

    ' Scale.
    gr.ScaleTransform(
        CSng(device_rect.Width / world_rect.Width),
        CSng(device_rect.Height / world_rect.Height),
        MatrixOrder.Append)

    ' Translate to move the origin to the center
    ' of the device coordinate rectangle.
    gr.TranslateTransform(
        CSng(device_rect.X + device_rect.Width / 2),
        CSng(device_rect.Y + device_rect.Height / 2),
        MatrixOrder.Append)
End Sub
                                                  
Advanced Transformations

The following code shows how a program can use this subroutine to position a drawing. The DrawSmiley subroutine draws a smiley face within the area 0 <= X <= 1, 0 <= Y <= 1. The Paint event handler creates a Rectangle representing the device coordinates where it wants to draw the smiley face. It draws the rectangle so that you can see where the target is. The code then creates a world coordinate rectangle representing the area where the DrawSmiley subroutine draws the face.

' Draw a smiley face in the rectangle
' 0 < = X < = 1, 0 < = Y < = 1.
Private Sub DrawSmiley(ByVal gr As Graphics)
    Using the_pen As New Pen(Color.Black, 0)
        gr.FillEllipse(Brushes.Yellow, 0, 0, 1, 1) ' Face.
        gr.DrawEllipse(the_pen, 0, 0, 1, 1)
        gr.DrawArc(the_pen, 0.2, 0.2, 0.6, 0.6, 0, 180) ' Smile.
        gr.FillEllipse(Brushes.Black, 0.4, 0.4, 0.2, 0.25) ' Nose
        gr.FillEllipse(Brushes.White, 0.25, 0.15, 0.2, 0.25) ' Left eye.
        gr.DrawEllipse(the_pen, 0.25, 0.15, 0.2, 0.25)
        gr.FillEllipse(Brushes.Black, 0.35, 0.2, 0.1, 0.15)
        gr.FillEllipse(Brushes.White, 0.55, 0.15, 0.2, 0.25) ' Right eye.
        gr.DrawEllipse(the_pen, 0.55, 0.15, 0.2, 0.25)
        gr.FillEllipse(Brushes.Black, 0.65, 0.2, 0.1, 0.15)
    End Using
End Sub

Private Sub Form1_Paint(ByVal sender As Object,
 ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
    ' Draw the target rectangle.
    Dim device_rect As New Rectangle(50, 50, 150, 150)
    e.Graphics.DrawRectangle(Pens.Black, device_rect)

    ' Map between world and device coordinate rectangles.
    Dim world_rect As New Rectangle(0, 0, 1, 1)
    MapRectangles(e.Graphics, world_rect, device_rect)

    ' Draw the smiley face.
    DrawSmiley(e.Graphics)
End Sub
                                                  
Advanced Transformations

Figure 30-11 shows the result. Note that the smiley face fits the target device coordinate rectangle nicely.

The preceding example uses subroutine MapRectangles to scale and translate a drawing, but the routine can also stretch or flip the drawing. For example, if the previous code used the following statement to define the device rectangle, the result has width 50 and height 150 so it is tall and thin:

Dim device_rect As New Rectangle(50, 50, 50, 150)
This program uses the MapRectangles subroutine to map a smiley face in world coordinates into a rectangle in device coordinates.

Figure 30.11. This program uses the MapRectangles subroutine to map a smiley face in world coordinates into a rectangle in device coordinates.

Subroutine MapRectangles can be particularly handy if you need to graph an equation. Normally, in device coordinates, the origin is in the upper-left corner and Y values increase downward. When you draw a graph, however, the origin is in the lower-left corner and Y values increase upward. You could work out how to modify the equation you are trying to draw so that it comes out in the proper orientation, but it's much easier to use subroutine MapRectangles to flip the Y coordinates.

To invert the Y coordinates, set the device coordinate Rectangle structure's Y property to the largest Y coordinate you want to use, and set its height to the negative of the height you really want to use. For example, suppose that you want the graph to fill the device coordinate rectangle 50 <= X <= 200, 50 <= Y <= 200. The rectangle has width and height 150, so you would use the following statement to map the coordinate windows:

Dim device_rect As New Rectangle(50, 200, 150, −150)

You could also use subroutine MapRectangles to flip a drawing's X coordinates, although the need for that is much less common.

Saving and Restoring Graphics State

The Graphics object's Save method takes a snapshot of the object's state and stores it in a GraphicsState object. Later, you can pass this GraphicsState object to the Graphics object's Restore method to return to the saved state.

Note that you can pass a particular GraphicsState object to the Restore method only once. If you want to use the same state again, you can call the Save method to save it again right after you restore it. That's the approach used by the following code. The program creates transformations that scale by a factor of 90 and then translate to move the origin to the center of the form. It then starts a loop to draw a rectangle rotated by angles between 5 degrees and 90 degrees.

Private Sub Form1_Paint(ByVal sender As Object,
 ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
    Using black_pen As New Pen(Color.Black, 0)
        ' Scale by a factor of 90.
        e.Graphics.ScaleTransform(90, 90, MatrixOrder.Append)

        ' Translate to center on the form.
        e.Graphics.TranslateTransform(
            Me.ClientRectangle.Width  2,
            Me.ClientRectangle.Height  2,
            MatrixOrder.Append)

        For i As Integer = 5 To 90 Step 5
            ' Save the state.
            Dim graphics_state As GraphicsState = e.Graphics.Save()

            ' Rotate i degrees.
            e.Graphics.RotateTransform(i, MatrixOrder.Prepend)

            ' Draw a rectangle.
            e.Graphics.DrawRectangle(black_pen, −1, −1, 2, 2)

            ' Restore the saved state.
            e.Graphics.Restore(graphics_state)
        Next i
    End Using
End Sub
                                                  
Saving and Restoring Graphics State

Within the loop it calls the Save method to record the Graphics object's current state. That state includes the initial scaling and translation. The loop then adds a rotation transformation to the Graphics object. It passes the RotateTransform method the value MatrixOrder.Prepend, so the rotation is added to the front of the transformation matrix. That makes the combined transformation apply the rotation before the scaling and translation. In other words, drawings are rotated, then scaled, and then translated.

The loop then draws the rectangle −1 <= X <= 1, −1 <= Y <= 1, and then calls Restore to restore the saved graphics state that holds just the scaling and translation.

Figure 30-12 shows the result.

There are usually many other ways to achieve the same effect in graphics programming. Though changing the order of transformations generally does not give the same result, you can always use different sets of transformations to produce the same outcome.

The following code shows a simpler For loop that creates the same result as the previous version. Rather than using Restore to remove the Graphics object's current rotation and replacing it with a new one, this version simply adds another 5-degree rotation to the current rotation. For example, a single 10-degree rotation is the same as two 5-degree rotations.

This program saves and restores the Graphics object's state containing a scaling and translation, adding an extra rotation as needed.

Figure 30.12. This program saves and restores the Graphics object's state containing a scaling and translation, adding an extra rotation as needed.

For i As Integer = 5 To 90 Step 5
    ' Rotate 5 degrees.
    e.Graphics.RotateTransform(5, MatrixOrder.Prepend)

     ' Draw a rectangle.
     Using black_pen As New Pen(Color.Black, 0)
         e.Graphics.DrawRectangle(black_pen, −1, −1, 2, 2)
     End Using
Next i
                                                  
This program saves and restores the Graphics object's state containing a scaling and translation, adding an extra rotation as needed.

Normally, you would not use Save and Restore if such a simple solution is available without them. Save and Restore are more useful when you want to perform several operations using the same transformation and other transformations are interspersed.

DRAWING EVENTS

When part of a control must be redrawn, it generates a Paint event. For example, if you minimize a form and then restore it, partially cover a form with another form, or enlarge a form, parts of the form must be redrawn.

The Paint event handler provides a parameter e of type PaintEventArgs. That parameter's Graphics property holds a reference to a Graphics object that the event handler should use to redraw the control. This Graphics object has its clipping region set to the part of the control that must be redrawn. For example, if you make a form wider, the Graphics object is clipped, so it only draws on the new piece of form on the right that was just exposed. Clipping the Graphics object makes drawing faster because the GDI+ routines can ignore drawing commands outside of the clipping region more quickly than they can draw them.

Clipping the Graphics object sometimes leads to unexpected results, particularly if the Paint event handler draws something that depends on the form's size. The following code draws a rectangle with an X in it, filling the form whenever the form resizes:

Private Sub Form1_Paint(ByVal sender As Object,
 ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
    e.Graphics.Clear(Me.BackColor)
    e.Graphics.DrawRectangle(Pens.Black, 0, 0,
        Me.ClientSize.Width − 1,
        Me.ClientSize.Height − 1)
    e.Graphics.DrawLine(Pens.Black, 0, 0,
        Me.ClientSize.Width − 1,
        Me.ClientSize.Height − 1)
    e.Graphics.DrawLine(Pens.Black,
        Me.ClientSize.Width − 1, 0,
        0, Me.ClientSize.Height − 1)
End Sub
                                                  
DRAWING EVENTS

Figure 30-13 shows the result after the form has been resized several times. Each time the form resizes, the Paint event handler draws the newly exposed region, but the existing drawing remains, giving the appearance of stacked envelopes.

Some computers generate Paint events every time the mouse moves during a resize, so the newly exposed areas are filled with a densely packed series of lines.

Paint event handlers also don't execute when the form shrinks. If the form shrinks, no new areas are exposed, so no Paint events fire.

Paint event handlers work well if the image on the control does not depend on the control's size. For example, if you want to draw an ellipse with bounds 10 <= X <= 300, 10 <= Y <= 10, then a Paint event handler works nicely. If a drawing depends on the control's size, you must also draw the picture in the control's Resize event handler.

Paint event handlers that adjust their drawings based on the form's size may produce unexpected results.

Figure 30.13. Paint event handlers that adjust their drawings based on the form's size may produce unexpected results.

You could implement the drawing code in the Paint and Resize event handlers, or call a drawing subroutine from those event handlers, but Visual Basic provides an easier way to deal with these issues. The following code shows how to make the form use its Paint event handler to draw itself whenever it is exposed or resized.

When the form loads, its Load event handler sets the form's ResizeRedraw property to True to indicate that the form should redraw itself when it is resized. Next the code calls the form's SetStyle method to set the AllPaintingInWmPaint style to True. This tells Visual Basic that the form does all of its drawing in its Paint event handler. Now, when the form resizes, Visual Basic raises the Paint event, so the program can do all of its drawing in the Paint event and not worry about the Resize event.

Private Sub Form1_Load() Handles MyBase.Load
    Me.ResizeRedraw = True
    Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
End Sub

Private Sub Form1_Paint(ByVal sender As Object,
 ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
    e.Graphics.Clear(Me.BackColor)
    e.Graphics.DrawRectangle(Pens.Black, 0, 0,
        Me.ClientSize.Width - 1,
        Me.ClientSize.Height - 1)
    e.Graphics.DrawLine(Pens.Black, 0, 0,
        Me.ClientSize.Width - 1,
        Me.ClientSize.Height - 1)
    e.Graphics.DrawLine(Pens.Black,
        Me.ClientSize.Width - 1, 0,
        0, Me.ClientSize.Height - 1)
End Sub
                                                  
Paint event handlers that adjust their drawings based on the form's size may produce unexpected results.

SUMMARY

Visual Basic .NET provides a huge number of graphical classes. This chapter provides an overview of these classes. It also focuses on the most important graphics class: Graphics. The Graphics class provides methods that let you draw and fill rectangles, ellipses, polygons, curves, lines, and other shapes. Pen and Brush objects determine the appearance of lines and filled areas. Other Graphics properties and methods let you determine the types of anti-aliasing used when drawing different kinds of objects, and how drawings are transformed.

Chapter 31 describes the next two most important graphics classes: Brush and Pen. It also explains the GraphicsPath object that you can use to draw and fill paths consisting of lines, shapes, curves, and text.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.46.96