Character Formatting

You can apply character formatting to text in a rich edit control by using the EM_SETCHARFORMAT message. To determine the current formatting of selected characters, use the EM_GETCHARFORMAT message. With either message, the application uses a pointer to the CHARFORMAT structure to specify character attributes. The following attributes are supported for characters:

Setting the effects is simply a matter of filling out the CHARFORMAT structure with the size of the structure (for versioning), specifying which attribute to alter, and sending the EM_SETCHARFORMAT message. In the RICHED sample, these effects are toggled, so the handler checks the current effect and toggles it. The code on the following page is the handler for the Bold command. The only differences between this handler and the handlers for italics and underlining are the dwMask field (CFM_ITALIC for italics and CFM_UNDERLINE for underlining) and the dwEffects field (CFE_ITALIC for italics and CFE_UNDERLINE for underlining).

void BoldCmd (HWND hWndRichEdit)
{
CHARFORMAT cf;

// Fill out the CHARFORMAT structure to set character effects.
cf.cbSize = sizeof (cf);
cf.dwMask = CFM_BOLD;

// Get the bold status.
SendMessage (hWndRichEdit, EM_GETCHARFORMAT, TRUE, (LPARAM)&cf);

// Toggle the bold effect.
cf.dwEffects ^= CFE_BOLD;

// Set the new bold status.
SendMessage (hWndRichEdit, EM_SETCHARFORMAT, SCF_SELECTION,
(LPARAM)&cf);
}

The default character formatting is applied to newly inserted text only if the current selection is empty. Otherwise, the new text assumes the character formatting of the text it replaces. If the selection changes, the default character formatting changes to match the first character in the new selection.

In the RICHED sample, the user can also pick a new typeface by using one of the drop-down combo boxes on the toolbar, shown in Figure 5-2.

 

Figure 5-2.

The combo box displaying typeface choices.

When the user chooses a typeface from the list, the application's ChangeFaceName function sends an EM_SETCHARFORMAT message to change the typeface. This function preserves the previous character effects (boldface, italics, and underlining).

VOID ChangeFaceName (HWND hWndRichEdit, LPTSTR lpFaceName)
{
CHARFORMAT cf;

// Fill out the CHARFORMAT structure to get the character effects.
cf.cbSize = sizeof (cf);
cf.dwMask = CFM_ITALIC | CFM_BOLD | CFM_UNDERLINE;
SendMessage (hWndRichEdit, EM_GETCHARFORMAT, TRUE, (LPARAM)&cf);

// Include the mask to ask the rich edit control for the current
// typeface.
cf.dwMask |= CFM_FACE;

// Set the new typeface, preserving the previous effects.
strcpy (cf.szFaceName, lpFaceName);
SendMessage (hWndRichEdit, EM_SETCHARFORMAT, SCF_SELECTION,
(LPARAM)&cf);
}

You might be wondering how I filled in the font choices. Well, like any smart developer, I looked for some sample code I could use. In the Win32 SDK, I found a sample called TTFONTS, which enumerates all the available fonts and allows the user to play around with the fields in the TEXTMETRIC and LOGFONT structures. I was able to use the font-enumerating code from TTFONTS and to use the structure that was defined to hold font information:

// Structure holding font information
typedef struct tagArFonts
{
int nFonts;
int cySpace;
HDC hdc;
LOGFONT *lf;
TEXTMETRIC *tm;
int *Type;
} ARFONTS, *PARFONTS;

The code that begins on the following page uses the EnumFonts function to get the number of fonts, allocates space for the font information, and fills out a structure for each font found. The only change I made was to add a filter for TrueType fonts (because my sample supports only these fonts). Filtering for TrueType fonts allowed me to make some assumptions about the font that the user will choose.

PARFONTS BuildFontList (HDC hdcIn, LPINT retnFaces)
{
nFaces = 0; // initialize global face count to 0
hdcGlobal = hdcIn; // save HDC for callbacks

// Count the number of typefaces.
EnumFonts (hdcGlobal, NULL, (FONTENUMPROC)MyEnumCount,
(LPARAM)&nFaces);

// Allocate the pointer to the array of ARFONTS structures.
parFontsGlobal = (PARFONTS) LocalAlloc (
LPTR, sizeof(ARFONTS) * (nFaces+1));

// Step through all fonts again. For each one, fill out a LOGFONT
// structure and a TEXTMETRIC structure.
iFace = 0;
EnumFonts (hdcGlobal, NULL, (FONTENUMPROC)MyEnumFaces, (LPARAM)NULL);

*retnFaces = nFaces;
return parFontsGlobal;
}

int APIENTRY MyEnumFaces (
LPLOGFONT lpLogFont,
LPTEXTMETRIC lpTEXTMETRICs,
DWORD fFontType,
LPVOID lpData)
{
int nFonts;

UNREFERENCED_PARAMETER (lpTEXTMETRICs);
UNREFERENCED_PARAMETER (fFontType);
UNREFERENCED_PARAMETER (lpData);

if (fFontType & TRUETYPE_FONTTYPE)
{
nFonts = 0;
EnumFonts (hdcGlobal, lpLogFont->lfFaceName,
(FONTENUMPROC)MyEnumCount, (LPARAM)&nFonts);

parFontsGlobal[iFace].lf = (LPLOGFONT) LocalAlloc (LPTR,
sizeof(LOGFONT) * nFonts);
parFontsGlobal[iFace].tm = (LPTEXTMETRIC) LocalAlloc (LPTR,
sizeof(TEXTMETRIC) * nFonts);
parFontsGlobal[iFace].Type = (LPINT) LocalAlloc (LPTR,
sizeof(int) * nFonts);

if ((parFontsGlobal[iFace].lf == NULL) ||
(parFontsGlobal[iFace].tm == NULL) ||
(parFontsGlobal[iFace].Type == NULL))
{
MessageBox (NULL, "alloc failed", NULL, MB_OK);
return FALSE;
}

parFontsGlobal[iFace].nFonts = nFonts;

jFont = 0;
EnumFonts (hdcGlobal, lpLogFont->lfFaceName,
(FONTENUMPROC)MyEnumCopy, (LPARAM)NULL);
iFace++;
}

return TRUE;
}

int APIENTRY MyEnumCount (
LPLOGFONT lpLogFont,
LPTEXTMETRIC lpTEXTMETRICs,
DWORD fFontType,
LPINT lpData)
{
UNREFERENCED_PARAMETER (lpLogFont);
UNREFERENCED_PARAMETER (lpTEXTMETRICs);
UNREFERENCED_PARAMETER (fFontType);

if (fFontType & TRUETYPE_FONTTYPE)
(*lpData)++;
return TRUE;
}

int APIENTRY MyEnumCopy (
LPLOGFONT lpLogFont,
LPTEXTMETRIC lpTEXTMETRICs,
DWORD fFontType,
LPVOID lpData)
{
LOGFONT *lplf;
TEXTMETRIC *lptm;
int *pType;

UNREFERENCED_PARAMETER (lpData);

if (fFontType & TRUETYPE_FONTTYPE)
{
lplf = parFontsGlobal[iFace].lf;
lptm = parFontsGlobal[iFace].tm;
pType = parFontsGlobal[iFace].Type;

lplf[jFont] = *lpLogFont;
lptm[jFont] = *lpTEXTMETRICs;
pType[jFont] = fFontType;

jFont++;
}
return TRUE;
}

When the structures are filled out, the names of the typefaces are inserted in the typeface combo box. The point size combo box is filled with an array of standard point sizes, as shown in Figure 5-3. The same point sizes are listed for each typeface. That's why I picked only TrueType fonts. Other kinds of fonts, such as raster fonts, are device-dependent, and you cannot make assumptions about their availability or about the point sizes they support. For a “real” word processor, the point size combo box can be filled dynamically when the user picks a typeface.

To change the point size, you simply use the same EM_SETCHARFORMAT message and specify the CFM_SIZE mask. Bear in mind that the point size is represented internally as twips, so you need to multiply the number the user chooses by 20. If you don't do this, you'll end up getting really tiny letters.

 

Figure 5-3.

The combo box displaying the point size options.

VOID ChangePointSize (HWND hWndRichEdit, int PointSize)
{
CHARFORMAT cf;

// Fill out the CHARFORMAT structure to set the point size.
cf.cbSize = sizeof (cf);
cf.dwMask = CFM_SIZE;

// Multiply by 20 to convert to twips.
cf.yHeight = PointSize * 20;

// Set the point size.
SendMessage (hWndRichEdit, EM_SETCHARFORMAT, SCF_SELECTION,
(LPARAM)&cf);

// Reset the dirty bit.
SendMessage (hWndRichEdit, EM_SETMODIFY, (WPARAM)TRUE, OL);
}

Notice in the RICHED sample that the appropriate toolbar buttons appear “pressed” and the corresponding menu items are checked when the user has chosen specific character effects, as shown in Figure 5-4. The sample accomplishes this task with a function called ToggleButton, which simply takes the command identifier of the button and a Boolean to toggle the button on and off. (The identifiers for the menu item and the button are the same.)

 

Figure 5-4.

Checked menu items and “pressed” toolbar buttons indicating character effects.

BOOL ToggleButton (HWND hWnd, int nID, BOOL bToggle)
{
if (bToggle)
{ // Uncheck the menu item and unpress the toolbar button.
CheckMenuItem (GetMenu(hWnd), nID, MF_BYCOMMAND | MF_UNCHECKED);
SendMessage (hWndToolbar, TB_CHECKBUTTON, nID, MAKELONG (FALSE, 0));
return FALSE;
}
else
{ // Check the menu item and press the toolbar button.
CheckMenuItem (GetMenu (hWnd), nID, MF_BYCOMMAND | MF_CHECKED);
SendMessage (hWndToolbar, TB_CHECKBUTTON, nID, MAKELONG (TRUE, 0));
return TRUE;
}
}

I did not implement the protected attribute or the color attribute in the RICHED sample. The protected character attribute allows the application to specify some text as read-only (without changing the appearance of the text by graying it out). If the user tries to modify protected text, the rich edit control sends its parent window an EN_PROTECTED notification, allowing the parent window to permit or prevent the change. This is useful for an application that lets the user change only specific items in a rich edit control, based on a password. To receive this notification, the application enables it by using the EM_SETEVENTMASK message, specifying ENM_PROTECTED.

The foreground color of a rich edit control is also a character attribute, but the background color is a property of the control. To set the background color, an application sends the EM_SETBKGNDCOLOR message. To set the foreground color, the application fills out the CHARFORMAT structure, specifying the CFM_COLOR attribute.