| Tenouk C & C++ | MFC Home | Event Handling, Mapping Modes & a Scrolling View 3 | GDI, Colors & Fonts 2 | Download | Site Index |


 

 

 

 

Module 4:

The Graphics Device Interface (GDI), Colors, and Fonts 1

 

 

 

 

Program examples compiled using Visual C++ 6.0 (MFC 6.0) compiler on Windows XP Pro machine with Service Pack 2. Topics and sub topics for this Tutorial are listed below:

  1. The Graphics Device Interface (GDI), Colors, and Fonts

  2. The Device Context Classes

  3. The Display Context Classes CClientDC and CWindowDC

  4. Constructing and Destroying CDC Objects

  5. The State of the Device Context

  6. The CPaintDC Class

  7. For Win32 Programmers

  8. GDI Objects

  9. Constructing and Destroying GDI Objects

  10. Tracking GDI Objects

  11. Stock GDI Objects

  12. The Lifetime of a GDI Selection

  13. Windows Color Mapping

  14. Standard Video Graphics Array Video Cards

  15. 256-Color Video Cards

  16. 16-Bit-Color Video Cards

  17. 24-Bit-Color Video Cards

  18. Fonts

  19. Fonts Are GDI Objects

  20. Choosing a Font

  21. Printing with Fonts

  22. Displaying Fonts

 

 

The Graphic Device Interface (GDI), Colors and Fonts

 

You've already seen some elements of the Graphics Device Interface (GDI). Anytime your program draws to the display or the printer, it must use the GDI functions. The GDI provides functions for drawing points, lines, rectangles, polygons, ellipses, bitmaps, and text. You can draw circles and squares intuitively once you study the available functions, but text programming is more difficult. This module gives you the information you need to start using the GDI effectively in the Microsoft Visual C++ environment. You'll learn how to use fonts on both the display and the printer.

 

The Device Context Classes

 

In the previous Modules, the view class's OnDraw() member function was passed a pointer to a device context object. OnDraw() selected a brush and then drew an ellipse. The Microsoft Windows device context is the key GDI element that represents a physical device. Each C++ device context object has an associated Windows device context, identified by a 32-bit handle of type HDC.

MFC Library version 6.0 provides a number of device context classes. The base class CDC has all the member functions (including some virtual functions) that you'll need for drawing. Except for the oddball CMetaFileDC class, derived classes are distinct only in their constructors and destructors. If you (or the application framework) construct an object of a derived device context class, you can pass a CDC pointer to a function such as OnDraw(). For the display, the usual derived classes are CClientDC and CWindowDC. For other devices, such as printers or memory buffers, you construct objects of the base class CDC. The "virtualness" of the CDC class is an important feature of the application framework. In Module 13, you'll see how easy it is to write code that works with both the printer and the display. A statement in OnDraw() such as:

 

// Print "Hello" at (0, 0) location

pDC->TextOut(0, 0, "Hello");

 

Sends text to the display, the printer, or the Print Preview window, depending on the class of the object referenced by the CView::OnDraw() function's pDC parameter. For display and printer device context objects, the application framework attaches the handle to the object. For other device contexts, such as the memory device context, you must call a member function after construction in order to attach the handle.

 

The Display Context Classes CClientDC and CWindowDC

 

Recall that a window's client area excludes the border, the caption bar, and the menu bar. If you create a CClientDC object, you have a device context that is mapped only to this client area - you can't draw outside it. The point (0, 0) usually refers to the upper-left corner of the client area.

As you'll see later, an MFC CView object corresponds to a child window that is contained inside a separate frame window, often along with a toolbar, a status bar, and scroll bars. The client area of the view, then, does not include these other windows. If the window contains a docked toolbar along the top, for example, (0, 0) refers to the point immediately under the left edge of the toolbar.

If you construct an object of class CWindowDC, the point (0, 0) is at the upper-left corner of the non-client area of the window. With this whole-window device context, you can draw in the window's border, in the caption area, and so forth. Don't forget that the view window doesn't have a non-client area, so CWindowDC is more applicable to frame windows than it is to view windows.

 

Constructing and Destroying CDC Objects

 

After you construct a CDC object, it is important to destroy it promptly when you're done with it. Microsoft Windows limits the number of available device contexts, and if you fail to release a Windows device context object, a small amount of memory is lost until your program exits. Most frequently, you'll construct a device context object inside a message handler function such as OnLButtonDown(). The easiest way to ensure that the device context object is destroyed (and that the underlying Windows device context is released) is to construct the object on the stack in the following way:

 

void CMyView::OnLButtonDown(UINT nFlags, CPoint point)

{

    CRect rect;

    // constructs dc on the stack

    CClientDC dc(this);

     // retrieves the clipping rectangle

     dc.GetClipBox(rect);

} // dc automatically released

 

Notice that the CClientDC constructor takes a window pointer as a parameter. The destructor for the CClientDC object is called when the function returns. You can also get a device context pointer by using the CWnd::GetDC member function, as shown in the following code. You must be careful here to call the ReleaseDC() function to release the device context.

 

void CMyView::OnLButtonDown(UINT nFlags, CPoint point)

{

    CRect rect;

 

    CDC* pDC = GetDC();    // a pointer to an internal dc

    pDC->GetClipBox(rect); // retrieves the clipping rectangle

    ReleaseDC(pDC);        // Don't forget this

}

 

You must not destroy the CDC object passed by the pointer to OnDraw(). The application framework handles the destruction for you.

 

The State of the Device Context

 

You already know that a device context is required for drawing. When you use a CDC object to draw an ellipse, for example, what you see on the screen (or on the printer's hard copy) depends on the current "state" of the device context. This state includes the following:

You have already seen, for example, that choosing a gray brush prior to drawing an ellipse results in the ellipse having a gray interior. When you create a device context object, it has certain default characteristics, such as a black pen for shape boundaries. All other state characteristics are assigned through CDC class member functions. GDI objects are selected into the device context by means of the overloaded SelectObject() functions. A device context can, for example, have one pen, one brush, or one font selected at any given time.

 

The CPaintDC Class

 

You'll need the CPaintDC class only if you override your view's OnPaint() function. The default OnPaint() calls OnDraw() with a properly set up device context, but sometimes you'll need display-specific drawing code. The CPaintDC class is special because its constructor and destructor do housekeeping unique to drawing to the display. Once you have a CDC pointer, however, you can use it as you would any other device context pointer. Here's a sample OnPaint() function that creates a CPaintDC object:

 

void CMyView::OnPaint()

{

    CPaintDC dc(this);

    OnPrepareDC(&dc); // explained later

    dc.TextOut(0, 0, "for the display, not the printer");

    OnDraw(&dc);      // stuff that's common to display and printer

}

 

For Win32 Programmers

 

The CPaintDC constructor calls BeginPaint() for you, and the destructor calls EndPaint(). If you construct your device context on the stack, the EndPaint() call is completely automatic.

 

GDI Objects

 

A Windows GDI object type is represented by an MFC library class. CGdiObject is the abstract base class for the GDI object classes. A Windows GDI object is represented by a C++ object of a class derived from CGdiObject. Here's a list of the GDI derived classes:

 

 

 

 

 

 

Derived Class

Description

CBitmap

A bitmap is an array of bits in which one or more bits correspond to each display pixel. You can use bitmaps to represent images, and you can use them to create brushes.

CBrush

A brush defines a bitmapped pattern of pixels that is used to fill areas with color.

CFont

A font is a complete collection of characters of a particular typeface and a particular size. Fonts are generally stored on disk as resources, and some are device-specific.

CPalette

A palette is a color mapping interface that allows an application to take full advantage of the color capability of an output device without interfering with other applications.

CPen

A pen is a tool for drawing lines and shape borders. You can specify a pen's color and thickness and whether it draws solid, dotted, or dashed lines.

CRgn

A region is an area whose shape is a polygon, an ellipse, or a combination of polygons and ellipses. You can use regions for filling, clipping, and mouse hit-testing.

 

Table 1.

 

Constructing and Destroying GDI Objects

 

You never construct an object of class CGdiObject; instead, you construct objects of the derived classes. Constructors for some GDI derived classes, such as CPen and CBrush, allow you to specify enough information to create the object in one step. Others, such as CFont and CRgn, require a second creation step. For these classes, you construct the C++ object with the default constructor and then you call a create function such as the CreateFont() or CreatePolygonRgn() function.

The CGdiObject class has a virtual destructor. The derived class destructors delete the Windows GDI objects that are attached to the C++ objects. If you construct an object of a class derived from CGdiObject, you must delete it prior to exiting the program. To delete a GDI object, you must first separate it from the device context. You'll see an example of this in the next section. Failure to delete a GDI object was a serious offense with Win16. GDI memory was not released until the user restarted Windows. With Win32, however, the GDI memory is owned by the process and is released when your program terminates. Still, an unreleased GDI bitmap object can waste a significant amount of memory.

 

Tracking GDI Objects

 

OK, so you know that you have to delete your GDI objects and that they must first be disconnected from their device contexts. How do you disconnect them? A member of the CDC::SelectObject family of functions does the work of selecting a GDI object into the device context, and in the process it returns a pointer to the previously selected object (which gets deselected in the process). Trouble is, you can't de-select the old object without selecting a new object. One easy way to track the objects is to "save" the original GDI object when you select your own GDI object and "restore" the original object when you're finished. Then you'll be ready to delete your own GDI object. Here's an example:

 

void CMyView::OnDraw(CDC* pDC)

{

    // black pen, 2 pixels wide

    CPen newPen(PS_DASHDOTDOT, 2, (COLORREF) 0);

    CPen* pOldPen = pDC->SelectObject(&newPen);

 

    pDC->MoveTo(10, 10);

    pDC->Lineto(110, 10);

    // newPen is deselected

    pDC->SelectObject(pOldPen);

} // newPen automatically destroyed on exit

 

When a device context object is destroyed, all its GDI objects are deselected. Thus, if you know that a device context will be destroyed before its selected GDI objects are destroyed, you don't have to de-select the objects. If, for example, you declare a pen as a view class data member (and you initialize it when you initialize the view), you don't have to de-select the pen inside OnDraw() because the device context, controlled by the view base class's OnPaint() handler, will be destroyed first.

 

Stock GDI Objects

 

Windows contains a number of stock GDI objects that you can use. Because these objects are part of Windows, you don't have to worry about deleting them. Windows ignores requests to delete stock objects. The MFC library function CDC::SelectStockObject selects a stock object into the device context and returns a pointer to the previously selected object, which it deselects. Stock objects are handy when you want to deselect your own non-stock GDI object prior to its destruction. You can use a stock object as an alternative to the "old" object you used in the previous example, as shown here:

 

void CMyView::OnDraw(CDC* pDC)

{

    // black pen, 2 pixels wide

    CPen newPen(PS_DASHDOTDOT, 2, (COLORREF) 0);

 

    pDC->SelectObject(&newPen);

    pDC->MoveTo(10, 10);

    pDC->Lineto(110, 10);

    // newPen is deselected

    pDC->SelectStockObject(BLACK_PEN);

} // newPen destroyed on exit

 

The Microsoft Foundation Class Reference lists, under CDC::SelectStockObject, the stock objects available for pens, brushes, fonts, and palettes.

 

The Lifetime of a GDI Selection

 

For the display device context, you get a "fresh" device context at the beginning of each message handler function. No GDI selections (or mapping modes or other device context settings) persist after your function exits. You must, therefore, set up your device context from scratch each time. The CView class virtual member function OnPrepareDC() is useful for setting the mapping mode, but you must manage your own GDI objects. For other device contexts, such as those for printers and memory buffers, your assignments can last longer. For these long-life device contexts, things get a little more complicated. The complexity results from the temporary nature of GDI C++ object pointers returned by the SelectObject() function. The temporary "object" will be destroyed by the application framework during the idle loop processing of the application, sometime after the handler function returns the call. You can't simply store the pointer in a class data member; instead, you must convert it to a Windows handle (the only permanent GDI identifier) with the GetSafeHdc() member function. Here's an example:

 

// m_pPrintFont points to a CFont object created in CMyView's constructor

// m_hOldFont is a CMyView data member of type HFONT, initialized to 0

void CMyView::SwitchToCourier(CDC* pDC)

{

    m_pPrintFont->CreateFont(30, 10, 0, 0, 400, FALSE, FALSE,

                             0, ANSI_CHARSET, OUT_DEFAULT_PRECIS,

                             CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,

                             DEFAULT_PITCH | FF_MODERN,

                             "Courier New"); // TrueType

    CFont* pOldFont = pDC->SelectObject(m_pPrintFont);

 

    // m_hOldFont is the CGdiObject public data member that stores the handle

    m_hOldFont = (HFONT) pOldFont->GetSafeHandle();

}

 

void CMyView:SwitchToOriginalFont(CDC* pDC)

{

    // FromHandle is a static member function that returns an object pointer

    if (m_hOldFont)

    {

        pDC->SelectObject(CFont::FromHandle(m_hOldFont));

    }

}

// m_pPrintFont is deleted in the CMyView destructor

 

Be careful when you delete an object whose pointer is returned by SelectObject(). If you've allocated the object yourself, you can delete it. If the pointer is temporary, as it will be for the object initially selected into the device context, you won't be able to delete the C++ object.

 

 

 

 

 

 

 

Windows Color Mapping

 

The Windows GDI provides a hardware-independent color interface. Your program supplies an "absolute" color code, and the GDI maps that code to a suitable color or color combination on your computer's video display. Most programmers of applications for Windows try to optimize their applications' color display for a few common video card categories.

 

Standard Video Graphics Array Video Cards

 

A standard Video Graphics Array (VGA) video card uses 18-bit color registers and thus has a palette of 262,144 colors. Because of video memory constraints, however, the standard VGA board accommodates 4-bit color codes, which means it can display only 16 colors at a time. Because Windows needs fixed colors for captions, borders, scroll bars, and so forth; your programs can use only 16 "standard" pure colors. You cannot conveniently access the other colors that the board can display. Each Windows color is represented by a combination of 8-bit "red," "green," and "blue" values. The 16 standard VGA "pure" (non-dithered) colors are shown in the table below. Color-oriented GDI functions accept 32-bit COLORREF parameters that contain 8-bit color codes each for red, green, and blue. The Windows RGB macro converts 8-bit red, green, and blue values to a COLORREF parameter. The following statement, when executed on a system with a standard VGA board, constructs a brush with a dithered color (one that consists of a pattern of pure-color pixels):

 

CBrush brush(RGB(128, 128, 192));

 

16 standard VGA colors

Red

Green

Blue

Color

0

0

0

Black

0

0

255

Blue

0

255

0

Green

0

255

255

Cyan

255

0

0

Red

255

0

255

Magenta

255

255

0

Yellow

255

255

255

White

0

0

128

Dark blue

0

128

0

Dark green

0

128

128

Dark cyan

128

0

0

Dark red

128

0

128

Dark magenta

128

128

0

Dark yellow

128

128

128

Dark gray

192

192

192

Light gray

 

Table 2: The R (Red), G (Green) and B (Blue) color combination..

 

The following statement (in your view's OnDraw() function) sets the text background to red:

 

pDC->SetBkColor(RGB(255, 0, 0));

 

The CDC functions SetBkColor() and SetTextColor() don't display dithered colors as the brush-oriented drawing functions do. If the dithered color pattern is too complex, the closest matching pure color is displayed.

 

256-Color Video Cards

 

Most video cards can accommodate 8-bit color codes at all resolutions, which means they can display 256 colors simultaneously. This 256-color mode is now considered to be the "lowest common denominator" for color programming.

If Windows is configured for a 256-color display card, your programs are limited to 20 standard pure colors unless you activate the Windows color palette system as supported by the MFC library CPalette class and the Windows API, in which case you can choose your 256 colors from a total of more than 16.7 million. In this Module, we'll assume that the Windows default color mapping is in effect. With an SVGA 256-color display driver installed, you get the 16 VGA colors listed in the previous table plus 4 more, for a total of 20. The following table lists the 4 additional colors.

 

4 extra SVGA 256-color

Red

Green

Blue

Color

192

220

192

Money green

166

202

240

Sky blue

255

251

240

Cream

160

160

164

Medium gray

 

Table 3: Extra RGB combination for SVGA 256 colors.

 

The RGB macro works much the same as it does with the standard VGA. If you specify one of the 20 standard colors for a brush, you get a pure color; otherwise, you get a dithered color. If you use the PALETTERGB macro instead, you don't get dithered colors; you get the closest matching standard pure color as defined by the current palette.

 

16-Bit-Color Video Cards

 

Most modern video cards support a resolution of 1024-by-768 pixels, and 1 MB of video memory can support 8-bit color at this resolution. If a video card has 2 MB of memory, it can support 16-bit color, with 5 bits each for red, green, and blue. This means that it can display 32,768 colors simultaneously. That sounds like a lot, but there are only 32 shades each of pure red, green, and blue. Often, a picture will look better in 8-bit-color mode with an appropriate palette selected. A forest scene, for example, can use up to 236 shades of green. Palettes are not supported in 16-bit-color mode.

 

24-Bit-Color Video Cards

 

High-end cards (which are becoming more widely used) support 24-bit color. This 24-bit capability enables the display of more than 16.7 million pure colors. If you're using a 24-bit card, you have direct access to all the colors. The RGB macro allows you to specify the exact colors you want. You'll need 2.5 MB of video memory, though, if you want 24-bit color at 1024-by-768-pixel resolution.

 

Fonts

 

Old-fashioned character-mode applications could display only the boring system font on the screen. Windows provides multiple device-independent fonts in variable sizes. The effective use of these Windows fonts can significantly energize an application with minimum programming effort. TrueType fonts, first introduced with Windows version 3.1, are even more effective and are easier to program than the previous device-dependent fonts. You'll see several example programs that use various fonts later in this Module.

 

Fonts Are GDI Objects

 

Fonts are an integral part of the Windows GDI. This means that fonts behave the same way other GDI objects do. They can be scaled and clipped, and they can be selected into a device context as a pen or a brush can be selected. All GDI rules about de-selection and deletion apply to fonts.

 

Choosing a Font

 

Choosing a Windows font used to be like going to a fruit stand and asking for "a piece of reddish-yellow fruit, with a stone inside, that weighs about 4 ounces." You might have gotten a peach or a plum or even a nectarine, and you could be sure that it wouldn't have weighed exactly 4 ounces. Once you took possession of the fruit, you could weigh it and check the fruit type. Now, with TrueType, you can specify the fruit type, but you still can't specify the exact weight.

Today you can choose between two font types - device-independent TrueType fonts and device-dependent fonts such as the Windows display System font and the LaserJet LinePrinter font or you can specify a font category and size and let Windows select the font for you. If you let Windows select the font, it will choose a TrueType font if possible. The MFC library provides a font selection dialog box tied to the currently selected printer, so there's little need for printer font guesswork. You let the user select the exact font and size for the printer, and then you approximate the display the best you can.

 

Printing with Fonts

 

For text-intensive applications, you'll probably want to specify printer font sizes in points (1 point = 1/72 inch). Why? Most, if not all, built-in printer fonts are defined in terms of points. The LaserJet LinePrinter font, for example, comes in one size, 8.5 point. You can specify TrueType fonts in any point size. If you work in points, you need a mapping mode that easily accommodates points. That's what MM_TWIPS is for. An 8.5-point font is 8.5 × 20, or 170, twips, and that's the character height you'll want to specify.

 

Displaying Fonts

 

If you're not worried about the display matching the printed output, you have a lot of flexibility. You can choose any of the scalable Windows TrueType fonts, or you can choose the fixed-size system fonts (stock objects). With the TrueType fonts, it doesn't much matter what mapping mode you use; simply choose a font height and go for it. No need to worry about points.

Matching printer fonts to make printed output match the screen presents some problems, but TrueType makes it easier than it used to be. Even if you're printing with TrueType fonts, however, you'll never quite get the display to match the printer output. Characters are ultimately displayed in pixels (or dots), and the width of a string of characters is equal to the sum of the pixel widths of its characters, possibly adjusted for kerning. The pixel width of the characters depends on the font, the mapping mode, and the resolution of the output device. Only if both the printer and the display were set to MM_TEXT mode (1 pixel or dot = 1 logical unit) would you get an exact correspondence. If you're using the CDC::GetTextExtent function to calculate line breaks, the screen breakpoint will occasionally be different from the printer breakpoint.

In the MFC Print Preview mode, line breaks occur exactly as they do on the printer, but the print quality in the preview window suffers in the process. If you're matching a printer-specific font on the screen, TrueType again makes the job easier. Windows substitutes the closest matching TrueType font. For the 8.5-point LinePrinter font, Windows comes pretty close with its Courier New font.

 

 

Continue on next module...part 2

 

 

 

 

 

 

Further reading and digging:

  1. MSDN MFC 7.0 class library online documentation.

  2. MSDN MFC 9.0 class library online documentation - latest version.

  3. Porting & Migrating your older programs.

  4. MSDN Library

  5. DCOM at MSDN.

  6. COM+ at MSDN.

  7. COM at MSDN.

  8. Windows data type.

  9. Win32 programming Tutorial.

  10. The best of C/C++, MFC, Windows and other related books.

  11. Unicode and Multi-byte character set: Story and program examples.

 

 


| Tenouk C & C++ | MFC Home | Event Handling, Mapping Modes & a Scrolling View 3 | GDI, Colors & Fonts 2 | Download | Site Index |