VC++ 6.0 ebook chapter index
Free counters!

Simple Text Output

Let's begin by looking at the different functions Windows provides for text output, the device context attributes that affect text, and the use of stock fonts.

The Text Drawing Functions

The most common text output function is the one I've used in many sample programs so far:

TextOut (hdc, xStart, yStart, pString, iCount) ;

The xStart and yStart arguments are the starting position of the string in logical coordinates. Normally, this is the point at which Windows begins drawing the upper left corner of the first character. TextOut requires a pointer to the character string and the length of the string. The function does not recognize NULL-terminated character strings.

The meaning of the xStart and yStart arguments to TextOut can be altered by the SetTextAlign function. The TA_LEFT, TA_RIGHT, and TA_CENTER flags affect how xStart is used to position the string horizontally. The default is TA_LEFT. If you specify TA_RIGHT in the SetTextAlign function, subsequent TextOut calls position the right side of the last character in the string at xStart. With TA_CENTER, the center of the string is positioned at xStart.

Similarly, the TA_TOP, TA_BOTTOM, and TA_BASELINE flags affect the vertical positioning. TA_TOP is the default, which means that the string is positioned so that yStart specifies the top of the characters in the string. Using TA_BOTTOM means that the string is positioned above yStart. You can use TA_BASELINE to position a string so that the baseline is at yStart. The baseline is the line below which descenders, such as those on the lowercase p, q, and y, hang.

If you call SetTextAlign with the TA_UPDATECP flag, Windows ignores the xStart and yStart arguments to TextOut and instead uses the current position previously set by MoveToEx or LineTo, or another function that changes the current position. The TA_UPDATECP flag also causes the TextOut function to update the current position to the end of the string (for TA_LEFT) or the beginning of the string (for TA_RIGHT). This is useful for displaying a line of text with multiple TextOut calls. When the horizontal positioning is TA_CENTER, the current position remains the same after a TextOut call.

You'll recall that displaying columnar text in the series of SYSMETS programs in Chapter 4 required that one TextOut call be used for each column. An alternative is the TabbedTextOut function:

TabbedTextOut (hdc, xStart, yStart, pString, iCount,
               iNumTabs, piTabStops, xTabOrigin) ;

If the text string contains embedded tab characters (`\t' or 0x09), TabbedTextOut will expand the tabs into spaces based on an array of integers you pass to the function.

The first five arguments to TabbedTextOut are the same as those to TextOut. The sixth argument is the number of tab stops, and the seventh argument is an array of tab stops in units of pixels. For example, if the average character width is 8 pixels and you want a tab stop every 5 characters, then this array would contain the numbers 40, 80, 120, and so forth, in ascending order.

If the sixth and seventh arguments are 0 or NULL, tab stops are set at every eight average character widths. If the sixth argument is 1, the seventh argument points to a single integer, which is repeated incrementally for multiple tab stops. (For example, if the sixth argument is 1 and the seventh argument points to a variable containing the number 30, tab stops are set at 30, 60, 90… pixels.) The last argument gives the logical x-coordinate of the starting position from which tab stops are measured. This might or might not be the same as the starting position of the string.

Another advanced text output function is ExtTextOut (the Ext prefix stands for extended):

ExtTextOut (hdc, xStart, yStart, iOptions, &rect,
               pString, iCount, pxDistance) ;

The fifth argument is a pointer to a rectangle structure. This is either a clipping rectangle, if iOptions is set to ETO_CLIPPED, or a background rectangle to be filled with the current background color, if iOptions is set to ETO_OPAQUE. You can specify both options or neither.

The last argument is an array of integers that specify the spacing between consecutive characters in the string. This allows a program to tighten or loosen intercharacter spacing, which is sometimes required for justifying a single word of text in a narrow column. The argument can be set to NULL for default character spacing.

A higher-level function for writing text is DrawText, which we first encountered in the HELLOWIN program in Chapter 3. Rather than specifying a coordinate starting position, you provide a structure of type RECT that defines a rectangle in which you want the text to appear:

DrawText (hdc, pString, iCount, &rect, iFormat) ;

As with the other text output functions, DrawText requires a pointer to the character string and the length of the string. However, if you use DrawText with NULL-terminated strings, you can set iCount to -1 and Windows will calculate the length of the string for you.

When iFormat is set to 0, Windows interprets the text as a series of lines that are separated by carriage-return characters (`\r' or 0x0D) or linefeed characters (`\n' or 0x0A). The text begins at the upper left corner of the rectangle. A carriage return or linefeed is interpreted as a "newline" character, so Windows breaks the current line and starts a new one. The new line begins at the left side of the rectangle, spaced one character height (without external leading) below the previous line. Any text, including parts of letters, that would be displayed to the right or below the bottom of the rectangle is clipped.

You can change the default operation of DrawText by using the iFormat argument, which consists of one or more flags. The DT_LEFT flag (which is the default) specifies a left-justified line, DT_RIGHT specifies a right-justified line, and DT_CENTER specifies a line centered between the left and right sides of the rectangle. Because the value of DT_LEFT is 0, you needn't include the identifier if you want text to be left-justified only.

If you don't want carriage returns or linefeeds to be interpreted as newline characters, you can include the identifier DT_SINGLELINE. Windows then interprets carriage returns and linefeeds as displayable characters rather than control characters. When using DT_SINGLELINE, you can also specify whether the line is to be placed at the top of the rectangle (DT_TOP, the default), at the bottom of the rectangle (DT_BOTTOM), or halfway between the top and bottom (DT_VCENTER, the V standing for vertical).

When displaying multiple lines of text, Windows normally breaks the lines at carriage returns or linefeeds only. If the lines are too long to fit in the rectangle, however, you can use the DT_WORDBREAK flag, which causes Windows to create breaks at the end of words within lines. For both single-line and multiple-line displays, Windows truncates any part of the text that falls outside the rectangle. You can override this by including the flag DT_NOCLIP, which also speeds up the operation of the function. When Windows spaces multiple lines of text, it normally uses the character height without external leading. If you prefer that external leading be included in the line spacing, use the flag DT_EXTERNALLEADING.

If your text contains tab characters (`\t' or 0x09), you need to include the flag DT_EXPANDTABS. By default, the tab stops are set at every eighth character position. You can specify a different tab setting by using the flag DT_TABSTOP, in which case the upper byte of iFormat contains the character-position number of each new tab stop. I recommend that you avoid using DT_TABSTOP, however, because the upper byte of iFormat is also used for some other flags.

The problem with the DT_TABSTOP flag is solved by a newer DrawTextEx function that has an extra argument:

DrawTextEx (hdc, pString, iCount, &rect, iFormat, &drawtextparams) ;

The last argument is a pointer to a DRAWTEXTPARAMS structure, which is defined like so:

typedef struct tagDRAWTEXTPARAMS
    UINT cbSize ;         // size of structure
    int  iTabLength ;     // size of each tab stop
    int  iLeftMargin ;    // left margin
    int  iRightMargin ;   // right margin
    UINT uiLengthDrawn ;  // receives number of characters processed

The middle three fields are in units that are increments of the average character width.

Device Context Attributes for Text

Besides SetTextAlign, discussed above, several other device context attributes affect text. In the default device context, the text color is black, but you can change that with

SetTextColor (hdc, rgbColor) ;

As with pen colors and hatch brush colors, Windows converts the value of rgbColor to a pure color. You can obtain the current text color by calling GetTextColor.

Windows displays text in a rectangular background area that it might or might not color based on the setting of the background mode. You can change the background mode using

SetBkMode (hdc, iMode) ;

where iMode is either OPAQUE or TRANSPARENT. The default background mode is OPAQUE, which means that Windows uses the background color to fill in the rectangular background. You can change the background color by using

SetBkColor (hdc, rgbColor) ;

The value of rgbColor is converted to that of a pure color. The default background color is white.

If two lines of text are too close to each other, the background rectangle of one can obscure the text of another. For this reason, I have often wished that the default background mode were TRANSPARENT. In the TRANSPARENT case, Windows ignores the background color and doesn't color the rectangular background area. Windows also uses the background mode and background color to color the spaces between dotted and dashed lines and the area between the hatches of hatched brushes, as I discussed in Chapter 5.

Many Windows programs specify WHITE_BRUSH as the brush that Windows uses to erase the background of a window. The brush is specified in the window class structure. However, you may want to make the background of your program's window consistent with the system colors that a user can set in the Control Panel program. In that case, you would specify the background color this way in the WNDCLASS structure:

wndclass.hbrBackground = COLOR_WINDOW + 1 ;

When you want to write text to the client area, you can then set the text color and background color using the current system colors:

SetTextColor (hdc, GetSysColor (COLOR_WINDOWTEXT)) ;
SetBkColor (hdc, GetSysColor (COLOR_WINDOW)) ;

If you do this, you'll want your program to be alerted if the system colors change:

      InvalidateRect (hwnd, NULL, TRUE) ;
      break ;

Another device context attribute that affects text is the intercharacter spacing. By default it's set to 0, which means that Windows doesn't add any space between characters. You can insert space by using the function

SetTextCharacterExtra (hdc, iExtra) ;

The iExtra argument is in logical units. Windows converts it to the nearest pixel, which can be 0. If you use a negative value for iExtra (perhaps in an attempt to squeeze characters closer together), Windows takes the absolute value of the number— you can't make the value less than 0. You can obtain the current intercharacter spacing by calling GetTextCharacterExtra. Windows converts the pixel spacing to logical units before returning the value.

Using Stock Fonts

When you call TextOut, TabbedTextOut, ExtTextOut, DrawText, or DrawTextEx to write text, Windows uses the font currently selected in the device context. The font defines a particular typeface and a size. The easiest way to display text with various fonts is to use the stock fonts that Windows provides. However, the range of these is quite limited.

You can obtain a handle to a stock font by calling

hFont = GetStockObject (iFont) ;

where iFont is one of several identifiers. You can then select that font into the device context:

SelectObject (hdc, hFont) ;

Or you can accomplish this in one step:

SelectObject (hdc, GetStockObject (iFont)) ;

The font selected in the default device context is called the system font and is identified by the GetStockObject argument SYSTEM_FONT. This is a proportional ANSI character set font. Specifying SYSTEM_FIXED_FONT in GetStockObject (which I did in a few programs earlier in this book) gives you a handle to a fixed-pitch font compatible with the system font used in versions of Windows prior to version 3. This is convenient when you need all the font characters to have the same width.

The stock OEM_FIXED_FONT, also called the Terminal font, is the font that Windows uses in MS-DOS Command Prompt windows. It incorporates a character set compatible with the original extended character set of the IBM PC. Windows uses DEFAULT_GUI_FONT for the text in window title bars, menus, and dialog boxes.

When you select a new font into a device context, you must calculate the font's character height and average character width using GetTextMetrics. If you've selected a proportional font, be aware that the average character width is really an average and that some characters have a lesser or greater width. Later in this chapter you'll learn how to determine the full width of a string made up of variable-width characters.

Although GetStockObject certainly offers the easiest access to different fonts, you don't have much control over which font Windows gives you. You'll see shortly how you can be very specific about the typeface and size that you want.