Visual C++ 6 Unleashed

Visual C++ 6 Unleashed

By MICKEY WILLIAMS and David Bennett

Brushes and Pens

So far, you have examined the CDC base class and its derivatives and looked at how to obtain and create device contexts in various situations. The main purpose of a device context, however, is to provide a uniform drawing surface for the GDI rendering func tions. These rendering functions use two main GDI objects to draw lines and filled areas. Lines are drawn with objects called pens, and filled areas are drawn with objects called brushes.

These two objects are represented at the Win32 level by the HPEN and HBRUSH handles. All of the GDI objects, including pens and brushes, are wrapped by the CGdiObject MFC base class. Although the CGdiObject is not strictly an abstract base class, you normally would construct one of its derived classes, such as CPen or CBrush.

CGdiObject is responsible for manipulating these handles and provides member functions such as Attach() and Detach() to attach the object to a GDI handle and detach it after use.

The handle itself is stored in the m_hObject member, which you can retrieve by calling the CGdiObject::GetSafeHandle() function. A static function (similar to the device contexts) called FromHandle() lets you dynamically create a CGdiObject for the duration of a Windows message. This object then is deleted by DeleteTempMap() when the thread returns to the Windows message loop.

You can find details about the underlying object by using the GetObject() function, which fills a structure with the object's attributes. The type of structure filled depends on the type of underlying object. The function itself just takes an LPVOID pointer to a buffer to receive the details and the size of the buffer.

The DeleteObject() function deletes the underlying GDI object from memory, so you are free to reuse the same CGdiObject by calling one of the derived classes'Create() functions to create another GDI object.

The CGdiObject class is extended by the MFC CPen and CBrush classes, which greatly simplify creating and manipulating these two drawing objects. This section shows you how these classes are constructed and used.

Pens and the CPen Class

Pens are used to draw lines, curves, and the boundaries of filled shapes, such as ellipses, polygons, and chords. You can create pens of various sizes, styles, and colors (even invisible pens for drawing shapes without outlines), and then select a pen into the device context and draw with it. You can select only one pen into a device context at any time. The old pen is swapped out automatically when you select a new pen.

There are two main pen types: cosmetic and geometric. The cosmetic pens are simple to create and quick to draw with. The geometric pens support more precise world units and let you use complex patterns and hatching effects in ways similar to brushes (described later in this chapter, in the section, "Brushes and the CBrush Class").

You can create a variety of pens using the CPen constructor. There are two versions of the constructor. You can pass one version of the constructor a set of style flags (as shown in Table 6.1), the pen width, and a COLORREF to indicate the color of the new pen.

Table 6.1. Simple Pen Styles

Style Pen Description
PS_SOLID Draws in a solid color.
PS_NULL Uses an invisible pen.
PS_DASH Draws in a dashed style.
PS_DASHDOT Draws alternating dashes and dots.
PS_DOT Draws in a dotted style.
PS_INSIDEFRAME Draws lines inside filled areas.

The other constructor form lets you supply a pointer to a LOGBRUSH structure to initialize the attributes of a geometric pen. You also can pass an array of DWORD values to specify complex dot and dash patterns. You can use some additional flags to change the mitering (how lines are joined) and end-cap styles (how lines end). Table 6.2 explains these flags.

Table 6.2. Advanced Pen Styles

Style Pen Description
PS_COSMETIC Specifies a cosmetic pen.
PS_GEOMETRIC Specifies a geometric pen.
PS_ALTERNATE Sets every other pixel (only cosmetic pens).
PS_USERSTYLE Uses the user-style DWORD array to create the dash-dot pattern.
PS_JOIN_ROUND Draws round, joined lines.
PS_JOIN_BEVEL Draws beveled, joined lines.
PS_JOIN_MITER Draws mitered, joined lines.
PS_ENDCAP_FLAT Makes the ends of lines flat.
PS_ENDCAP_ROUND Makes the ends of lines round.
PS_ENDCAP_SQUARE Makes the ends of lines square.

You also can construct an uninitialized pen by using the default constructor. You then initialize it by using CreatePen() and passing the same parameters as the two constructors for the CPen class, or by using CreatePenIndirect() and passing a pointer to a LOGPEN structure that holds the pen-initialization details.

Selecting Pens into the Device Context

After you create a pen, you must select it into a device context in order for the drawing functions to start using that pen. You can select all of the GDI objects by using the device context's overloaded SelectObject() function.

The SelectObject() function that accepts a CPen object returns a pointer to the currently selected CPen object. When you select your new object, the current object is deselected automatically. You should save the pointer to this deselected object to reselect it when you finish drawing with your pen.

You also can use the device context's SaveDC() and RestoreDC() functions to reselect the original pens and release your created pen. (Remember that this action also restores all the other GDI objects you have selected.)

Using Stock Pens

The operating system can lend out a number of common GDI objects to applications; these objects are called stock objects. These include a white pen, a black pen, and a null pen. You can select these straight into a device context by calling the CDC::SelectStockObject() function and passing the WHITE_PEN, BLACK_PEN, or NULL_PEN index values. As usual, the currently selected pen is returned from SelectStockObject().

You also can use the pen's base class CGdiObject::CreateStockObject() function, passing the same index values to create a stock CPen object.

Drawing with Pens

A number of rendering functions use just pens. Other functions use brushes to fill an area and pens to draw the outline of that area. You also can use the area-filling functions to draw only the outline of a shape by selecting a NULL brush.

The device context stores a current graphics cursor position, and many of the line-drawing functions use their starting point as the current graphics cursor position. You can change this position without drawing a line by using the device context's MoveTo() function. You can find the current graphics position from GetCurrentPosition().

The LineTo() function draws a line from the current position to the specified position and updates the graphics cursor to the specified endpoint.

You can use the Polyline() function to draw a number of lines from coordinates stored in an array of POINT structures, and you can use PolyPolyLine() to draw a number of independent line sections. The PolylineTo() function performs a task similar to Polyline() but also updates the current graphics cursor. You can plot a number of B[as]ezier splines (special type of curves) by using the PolyBezier() and PolyBezierTo() functions. The PolyDraw() function lets you draw a number of con nected lines and B[as]ezier splines.

You can draw elliptical arcs by using Arc(), ArcTo(), and AngleArc(). You can set the device context's default direction for these arcs to clockwise or counterclockwise by passing AD_CLOCKWISE or AD_COUNTERCLOCKWISE to the device context's SetArcDirection() function. This value also affects the rendering of chords drawn with the Chord() function. You can find the current direction by the flag returned from GetArcDirection().

You can call the area-filling functions to outline the areas with the current pen, as discussed in the following sections about brushes.

The sample OnDraw() function shown in Listing 6.1 draws a number of shapes using different drawing functions in a variety of pens to produce the output shown in Figure 6.1.

06fig01.jpg

Figure 6.1 Drawing with various pen styles.

Example 6.1. An OnDraw() Function Using Different Pens to Create Graphics

void CPensDemoView::OnDraw(CDC* pDC)
{
    CPen penRed(PS_SOLID,5,RGB(255,0,0));
    CPen penThick(PS_SOLID,10,RGB(0,0,255));
    CPen penDash(PS_DASH,1,RGB(0,128,0));
    CPen penDot(PS_DOT,1,RGB(255,0,255));
    CPen penDashDot(PS_DASHDOT,1,RGB(0,0,0));

    CRect rcClient;
    GetClientRect(&rcClient);
    int nSaved = pDC->SaveDC();
    pDC->SelectStockObject(NULL_BRUSH); // No Area Filling

    for(int i=0;i<rcClient.Height()/6;i++)
    {
        int x=i%3,y=((i%6)/3);
        int w=rcClient.Width()/3, h=rcClient.Height()/2;
        CRect rcDrw(x*w,y*h,x*w+w,y*h+h);
        rcDrw.DeflateRect(CSize(i,i));
        switch(i%6)
        {
            case 0:
                pDC->SelectObject(&penThick);
                pDC->MoveTo(rcDrw.TopLeft());
                pDC->LineTo(rcDrw.BottomRight());
                pDC->MoveTo(rcDrw.right,rcDrw.top);
                pDC->LineTo(rcDrw.left,rcDrw.bottom);
                break;
            case 1:
                pDC->SelectObject(&penRed);
                pDC->Ellipse(rcDrw);
                break;
            case 2:
            {
                POINT pts[] = {
                    {(short)rcDrw.left,(short)rcDrw.bottom},
                    {(short)rcDrw.right,(short)rcDrw.bottom},
                    {(short)rcDrw.left+rcDrw.Width()/2,
                        (short)rcDrw.top},
                    {(short)rcDrw.left,(short)rcDrw.bottom}};
                pDC->SelectObject(&penDash);
                pDC->Polyline(pts,sizeof(pts)/sizeof(POINT));
                break;
            }
            case 3:
                pDC->SelectObject(&penDot);
                pDC->Rectangle(rcDrw);
                break;
            case 4:
                pDC->SelectObject(&penDashDot);
                pDC->Pie(rcDrw,CPoint(rcDrw.right,rcDrw.top),
                            rcDrw.BottomRight());
                break;
            case 5:
                pDC->SelectStockObject(BLACK_PEN);
                pDC->Chord(rcDrw,rcDrw.TopLeft(),
                            rcDrw.BottomRight());
                break;
        }
    }
    pDC->RestoreDC(nSaved);
}
						

Brushes and the CBrush Class

Whereas pens are used to draw lines, brushes are used to fill areas and shapes. The CBrush class wraps the GDI brush object and, like other MFC GDI wrapper classes, is derived from the CGdiObject base class.

You can create brushes that fill an area with a solid color, a pattern from a bitmap, or a hatching scheme. You can use one of three constructor functions to construct brushes initialized with one of these filling techniques. One form lets you pass a COLORREF value for a solid-color brush. Another lets you pass a pointer to a CBitmap object to create a filling pattern. The third form lets you pass an index describing a hatching technique (as shown in Table 6.3) and a COLORREF value to specify the hatching-pattern color.

Table 6.3. Brush Hatching-Pattern Flags

Style Brush Hatching
HS_DIAGCROSS Diagonal crisscross
HS_CROSS Horizontal crisscross
HS_HORIZONTAL Horizontal lines
HS_VERTICAL Vertical lines
HS_FDIAGONAL Bottom-left to top-right lines
HS_BDIAGONAL Top-left to bottom-right lines

You can use the default constructor and then one of the creation functions—such as CreateSolidBrush(), CreateHatchBrush(), or CreatePatternBrush()—to create the GDI brush object. The CreateBrushIndirect() function lets you initialize and pass a pointer to a LOGBRUSH structure to create the brush. You can set the style from a number of flag values to indicate what sort of brush you want with the lbStyle member of LOGBRUSH. The other members are lbColor for the desired color and lbHatch to specify any hatching-style flags (remember that the same structure is used with geometric pens).

You can use the CreateDIBPatternBrush() creation function to create a patterned brush from a device-independent bitmap (DIB). You can use a CreateSysColorbrush() function to create a brush initialized with one of the current system colors specified by an index value.

Selecting Brushes into the Device Context

You can select a brush into a device context in the same way you would a pen. The device context's SelectObject() function has an overload that accepts a pointer to a CBrush object and returns the previously selected CBrush object.

After you select your new brush, it can use any of the drawing functions that draw filled shapes. As with the pen, you must ensure that the brush is selected out of the device context before it falls out of scope or is deleted.

Using Stock Brushes

You can select a number of stock brushes into a device context with the SelectStockObject() function, as Table 6.4 shows.

Table 6.4. Stock Brush Objects

Stock Object Flag Brush Description
WHITE_BRUSH White
LT_GRAY_BRUSH Light gray
GRAY_BRUSH Gray
DKGRAY_BRUSH Dark gray
BLACK_BRUSH Black
HOLLOW_BRUSH, NULL_BRUSH Transparent, like the NULL pen

You also can use the CGdiObject base class's CreateStockObject() function to create a CBrush object initialized from a stock object flag.

Drawing with Brushes

You can use a number of device-context member functions with a brush to draw filled shapes. These shapes are outlined with the currently selected pen.

Some functions, such as FillRect(), let you pass a CBrush object by pointer to draw with that brush. Other functions draw a rectangle from a COLORREF value, such as FillSolidRect(). Most functions, however, use the currently selected brush, such as the Rectangle() function. FillRect(), Rectangle(), and FillSolidRect() all render a filled rectangle from a set of rectangle coordinates. The RoundRect() function lets you draw rectangles with rounded corners.

You can draw filled polygons by using the Polygon() or PolyPolygon() function, which draws a single polygon or number of filled polygons, respectively. These two functions are similar to the Polyline() functions, because they take an array of POINT variables to define the polygon's vertices. When drawing polygons, you can specify one of two filling techniques: winding or alternate. These techniques affect which areas of a crisscrossing polygon are designated as filled. You can set these modes by using the SetPolyFillMode() function and passing the WINDING or ALTERNATE flag value.

You can draw circles and ellipses using the Ellipse() function, pie segments using Pie(), and chord sections with the Chord() function.

The FloodFill() function lets you fill an area bounded by a specified color with the current brush. You can use ExtFloodFill() to fill an area bounded by a specific color or fill an area of a specific color with the current brush.

Share ThisShare This

Informit Network