One of the first things that you need to know when working with fonts is that not every system that your applications run on will have the same fonts installed. Fonts are specified in files that can be installed and removed from Windows systems with relative ease. Every computer user can customize his system with whatever combination of fonts he wants. If you specify a font that doesn't exist on the system, Windows will choose either the system default font or what the operating system considers to be a reasonably close alternative font.
What you can do instead is ask the operating system what fonts are available. This method allows you to make your own decisions on which font to use or let the user make the decision. When you ask what fonts are available, you can limit the types of fonts that are listed, or you can choose to list them all and select various fonts based on various attributes.
To get a list of all available fonts on a computer, you call a Windows API (Application Programming Interface) function called EnumFontFamiliesEx. This function
One of the key arguments to the EnumFontFamiliesEx function is the address of another function. This second function is what is known as a callback function, which is called by the operating system. For almost every enumeration function in the Windows operating system, you pass the address of a callback function as an argument because the callback function is called once for each of the elements in the enumerated list. In other words, you have to include a function in your application to receive each individual font that is on the system and then build the list of fonts yourself.
When you create this function to receive each font and build your list, you cannot define your callback function in any way you want. All callback functions are already defined in the Windows API. You have to use a specific type of callback function to receive the list of fonts. For getting a list of fonts, the function type is EnumFontFamProc. This function type specifies how your function must be defined, what its arguments must be, and what type of return value it must return. It does not specify what your function should be named or how it needs to work internally. These aspects are left completely up to you.
The EnumFontFamiliesEx function, which you call to request the list of available fonts, takes five arguments. A typical use of this function
// Create a device context variable CClientDC dc (this); // Declare a LOGFONT structure LOGFONT lLogFont; // Specify the character set lLogFont.lfCharSet = DEFAULT_CHARSET; // Specify all fonts lLogFont.lfFaceName[0] = NULL; // Must be zero unless Hebrew or Arabic lLogFont.lfPitchAndFamily = 0; // Enumerate the font families ::EnumFontFamiliesEx((HDC) dc, &lLogFont, (FONTENUMPROC) EnumFontFamProc, (LPARAM) this, 0);
The first argument is a device context, which can be an instance of the CClientDC class. Every application running within the Windows operating system has a device context. The device context provides a lot of necessary information to the operating system about what is available to the application and what is not.
The second argument is a pointer to a LOGFONT structure. This structure contains information about the fonts that you want listed. You can specify in this structure which character set you want to list or whether you want all the fonts in a particular font family. If you want all the fonts on the system, you pass NULL in the place of this argument.
The third argument is the address of the callback function that will be used to build your list of fonts. Passing the address of your callback function is a simple matter of using the function name as the argument. The Visual C++ compiler takes care of replacing the function
The fourth argument is a LPARAM value that will be passed to the callback function. This parameter is not used by Windows but provides your callback function with a context in which to build the font list. In the example, the value being passed is a pointer to the window in which the code is being run. This way, the callback function can use this pointer to access any structures it needs to build the list of fonts. This pointer can also be the first node in a linked list of fonts or other such structure.
The fifth and final argument is always 0. This reserved argument may be used in future versions of Windows, but for now, it must be 0 so that your application
When you create your callback function, it must be defined as an independent function, not as a member of any C++ class. A typical EnumFontFamProc function declaration follows:
int CALLBACK EnumFontFamProc(
LPENUMLOGFONT lpelf,
LPNEWTEXTMETRIC lpntm,
DWORD nFontType,
long lParam)
{
// Create a pointer to the dialog window
CMyDlg* pWnd = (CMyDlg*) lParam;
// Add the font name to the list box
pWnd->m_ctlFontList.AddString(lpelf->elfLogFont.lfFaceName);
// Return 1 to continue font enumeration
return 1;
}
The first argument to this function is a pointer to an ENUMLOGFONTEX structure. This structure contains information about the logical attributes of the font, including the font name, style, and script. You may have
The second argument is a pointer to a NEWTEXTMETRICEX structure. This structure contains information about the physical attributes of the font, such as height, width, and space around the font. These values are all relative in nature because they need to scale as the font is made larger or smaller.
The third argument is a flag that specifies the type of font. This value may contain a combination of the following values:
Finally, the fourth argument is the value that was passed into the EnumFontFamiliesEx function. In the example, it was a pointer to the dialog on which the list of fonts is being built. If you cast this value as a pointer to the dialog, the function can access a list box control to add the font
The return value from this function determines whether the listing of fonts continues. If 0 is returned from this function, the operating system quits listing the available fonts. If 1 is returned, the operating system continues to list the available fonts.
To use a particular font in an application, you call an instance of the CFont class. By calling the CreateFont method, you can specify the font to be used, along with the
CFont m_fFont; // The font to be used
// Create the font to be used
m_fFont.CreateFont(12, 0, 0, 0, FW_NORMAL,
0, 0, 0, DEFAULT_CHARSET, OUT_CHARACTER_PRECIS,
CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH
FF_DONTCARE, m_sFontName);
// Set the font for the display area
m_ctlDisplayText.SetFont(&m_fFont);
TIP: The CFont variable used in the previous code should be declared as a member variable of the class in which this code is placed. In the sample code, it is declared above where it is used to show how it is declared. This variable should not be declared or used as a local variable in a function.
Seems simple enough--just two function calls--but that CreateFont function needs an
To understand how the CreateFont function works, let's look at the individual arguments that you have to pass to it. The function is defined as
BOOL CreateFont(
int nHeight,
int nWidth,
int nEscapement,
int nOrientation,
int nWeight,
BYTE bItalic,
BYTE bUnderline,
BYTE cStrikeOut,
BYTE nCharSet,
BYTE nOutPrecision,
BYTE nClipPrecision,
BYTE nQuality,
BYTE nPitchAndFamily,
LPCTSTR lpszFaceName);
The first of these arguments, nHeight, specifies the height of the font to be used. This logical value is translated into a physical value. If the value is 0, a reasonable default value is used. If the value is greater or less than 0, the absolute height is converted into device units. It is key to understand that height values of 10 and -10 are basically the same.
The second argument, nWidth, specifies the average width of the
The third argument, nEscapement, determines the angle at which the text will be printed. This value is specified in 0.1-degree units in a counterclockwise pattern. If you want to print vertical text that reads from bottom to top, you supply 900 as the value for this argument. For printing normal horizontal text that flows from left to right, supply 0 as this value.
The fourth argument, nOrientation, determines the angle of each individual character in the font. This works on the same basis as the previous argument, but it controls the output on a character basis, not a line-of-text basis. To print upside-down characters, set this value to 1800. To print characters on their backs, set this value to 900.
The fifth argument, nWeight, specifies the weight, or boldness, of the font. This can be any value from 0 to 1000, with 1000 being heavily bolded. You can use constants defined for this argument to control this value with ease and consistency. These constants are listed in Table 7.1.
| Constant | Value |
| FW_DONTCARE | |
| FW_THIN | 100 |
| FW_EXTRALIGHT | 200 |
| FW_ULTRALIGHT | 200 |
| FW_LIGHT | 300 |
| FW_NORMAL | 400 |
| FW_REGULAR | 400 |
| FW_MEDIUM | 500 |
| Constant | Value |
| FW_SEMIBOLD | 600 |
| FW_DEMIBOLD | 600 |
| FW_BOLD | 700 |
| FW_EXTRABOLD | 800 |
| FW_ULTRABOLD | 800 |
| FW_BLACK | 900 |
| FW_HEAVY | 900 |
The actual interpretation and availability of these weights depend on the font. Some fonts only have FW_NORMAL, FW_REGULAR, and FW_BOLD weights. If you specify FW_DONTCARE, a default weight is used, just as with most of the rest of the arguments.
The
The seventh argument, bUnderline, specifies whether the font is to be underlined. This is also a boolean value; 0 indicates that the font is not underlined, and any other value indicates that the font is
The
The ninth argument, nCharSet, specifies the font's character set. The available constants for this value are listed in Table 7.2.
| Constant | Value |
| ANSI_CHARSET | |
| DEFAULT_CHARSET | 1 |
| SYMBOL_CHARSET | 2 |
| SHIFTJIS_CHARSET | 128 |
| OEM_CHARSET | 255 |
The system on which your application is running might have other character sets, and the OEM character set is system dependent, making it different for systems from different manufacturers. If you are using one of these character sets, it is risky to try to manipulate the strings to be output, so it's best to just pass along the string to be displayed.
The tenth argument, nOutPrecision, specifies how closely the output must match the
The OUT_DEVICE_PRECIS, OUT_RASTER_PRECIS, and OUT_TT_PRECIS values control which font is
The eleventh argument, nClipPrecision, specifies how to clip characters that are partially outside of the display area. The values for this argument are
These values can be ORed together to specify a combination of clipping techniques.
The twelfth argument, nQuality, specifies the output quality and how
The thirteenth argument, nPitchAndFamily, specifies the pitch and family of the font. This value consists of two values that are ORed together to create a combination value. The first set of available values is
This value specifies the pitch to be used with the font. The second set of available values specifies the family of fonts to be used. The available values for this portion of the argument are
The font family describes in a general way the appearance of a font. You can use the font family value to choose an alternative font when a specific font does not exist on a system. The final argument, lpszFacename, is a standard C-style string that contains the name of the font to be used. This font name comes from the font information received by the EnumFontFamProc callback function.
Today you will build an application that allows the user to select from a list of available fonts to be displayed. The user will be able to enter some text to be displayed in the selected font, allowing the user to see what the font looks like.
To begin today's application, follow these steps:
FIGURE 7.1. The main dialog layout.
| Object | Property | Setting |
| Static Text | ID | IDC_STATIC |
| Caption | &Enter Some Text: | |
| Edit Box | ID | IDC_ESAMPTEXT |
| Static Text | ID | IDC_STATIC |
| Caption | &Select a Font | |
| List Box | ID | IDC_LFONTS |
|
|
ID | IDC_STATIC |
| Caption | Font Sample | |
| Static Text | ID | IDC_DISPLAYTEXT |
| (inside group box; size to | Caption | Empty string |
| fill the group box) | ||
| Command Button | ID | IDC_EXIT |
| Caption | E&xit |
| Object | Name | Category | Type |
| IDC_DISPLAYTEXT | m_ctlDisplayText | Control | CStatic |
| m_strDisplayText | Value | CString | |
| IDC_LFONTS | m_ctlFontList | Control | CListBox |
| m_strFontName | Value | CString | |
| IDC_ESAMPTEXT | m_strSampText | Value | CString |
To be able to create your list of fonts, you need to add your callback function to get each font list and add it to the list box that you placed on the dialog window. To do this, edit the Day7Dlg.h header file and add the function declaration in Listing 7.1 near the top of the file. This function cannot be added through any of the tools available in Visual C++. You need to
1: #if _MSC_VER > 1000 2: #pragma once 3: #endif // _MSC_VER > 1000 4: 5: int CALLBACK EnumFontFamProc(LPENUMLOGFONT lpelf, 6: LPNEWTEXTMETRIC lpntm, DWORD nFontType, long lParam); 7: 8: //////////////////////////////////////////////////////////////////// 9: // CDay7Dlg dialog 10: 11: class CDay7Dlg : public CDialog 12: . 13: .
14: .
Once you add the function declaration to the header file, open the Day7Dlg.cpp source-code file, scroll to the bottom of the file, and add the function definition in Listing 7.2.
1: int CALLBACK EnumFontFamProc(LPENUMLOGFONT lpelf,
2: LPNEWTEXTMETRIC lpntm, DWORD nFontType, long lParam)
3: {
4: // Create a pointer to the dialog window
5: CDay7Dlg* pWnd = (CDay7Dlg*) lParam;
6:
7: // Add the font name to the list box
8: pWnd->m_ctlFontList.AddString(lpelf->elfLogFont.lfFaceName);
9: // Return 1 to continue font enumeration
10: return 1;
11: }
Now that you have the callback function defined, you need to add a function to request the list of fonts from the operating system. To add this function, follow these steps:
1: void CDay7Dlg::FillFontList()
2: {
3: LOGFONT lf;
4:
5: // Initialize the LOGFONT structure
6: lf.lfCharSet = DEFAULT_CHARSET;
7: strcpy(lf.lfFaceName, "");
8: // Clear the list box
9: m_ctlFontList.ResetContent();
10: // Create a device context variable
11: CClientDC dc (this);
12: // Enumerate the font families
13: ::EnumFontFamiliesEx((HDC) dc, &lf,
14: (FONTENUMPROC) EnumFontFamProc, (LPARAM) this, 0);
15: }
1: BOOL CDay7Dlg::OnInitDialog()
2: {
3: CDialog::OnInitDialog();
4: .
5: .
6: .
7: // TODO: Add extra initialization here
8:
9: ///////////////////////
10: // MY CODE STARTS HERE
11: ///////////////////////
12:
13: // Fill the font list box
14: FillFontList();
15:
16: ///////////////////////
17: // MY CODE ENDS HERE
18: ///////////////////////
19:
20: return TRUE; // return TRUE unless you set the focus to a control
21: }
If you compile and run your application now, you should find that your list box is filled with the names of all the fonts available on the system. However, there's one aspect of this list that you probably don't want in your application. Figure 7.2 shows many duplicate entries in the list of fonts in the list box. It would be nice if you could eliminate these duplicates and have only one line per font.
FIGURE 7.2. Listing all the fonts in the system.
It turns out that the EnumFontFamiliesEx function call is synchronous in nature. This means that it doesn't return until all the fonts in the system are listed in calls to your callback function. You can place code in the FillFontList function to remove all the duplicate entries once the list box is filled. To do this, modify the FillFontList function as in Listing 7.5.
Listing 7.5. The modified FILLFONTLIST function.
1: void CDay7Dlg::FillFontList()
2: {
3: int iCount; // The number of fonts
4: int iCurCount; // The current font
5: CString strCurFont; // The current font name
6: CString strPrevFont = ""; // The previous font name
7: LOGFONT lf;
8:
9: // Initialize the LOGFONT structure
10: lf.lfCharSet = DEFAULT_CHARSET;
11: strcpy(lf.lfFaceName, "");
12: // Clear the list box
13: m_ctlFontList.ResetContent();
14: // Create a device context variable
15: CClientDC dc (this);
16: // Enumerate the font families
17: ::EnumFontFamiliesEx((HDC) dc, &lf,
18: (FONTENUMPROC) EnumFontFamProc, (LPARAM) this, 0);
19: // Get the number of fonts in the list box
20: iCount = m_ctlFontList.GetCount();
21: // Loop from the last entry in the list box to the first,
22: // searching for and deleting the duplicate entries
23: for (iCurCount = iCount; iCurCount > 0; iCurCount--)
24: {
25: // Get the current font name
26: m_ctlFontList.GetText((iCurCount - 1), strCurFont);
27: // Is it the same as the previous font name?
28: if (strCurFont == strPrevFont)
29: {
30: // If yes, then delete it
31: m_ctlFontList.DeleteString((iCurCount - 1));
32: }
33: // Set the previous font name to the current font name
34: strPrevFont = strCurFont;
35: }
36: }
Notice that the for loop started at the end of the list and worked backward. This allowed you to delete the current entry without worrying about manipulating the loop counter to prevent skipping lines in the list box. If you compile and run your application, there shouldn't be any duplicate entries in the list of available fonts.
Before you can display the font for the user, you need to place some text into the display area. The edit box near the top of the dialog is where the user enters text to be displayed in the font selected. To add the functionality, do the following:
1: BOOL CDay7Dlg::OnInitDialog()
2: {
3: CDialog::OnInitDialog();
4: .
5: .
6: .
7: // TODO: Add extra initialization here
8:
9: ///////////////////////
10: // MY CODE STARTS HERE
11: ///////////////////////
12:
13: // Fill the font list box
14: FillFontList();
15:
16: // Initialize the text to be entered
17: m_strSampText = "Testing";
18: // Copy the text to the font sample area
19: m_strDisplayText = m_strSampText;
20: // Update the dialog
21: UpdateData(FALSE);
22:
23: ///////////////////////
24: // MY CODE ENDS HERE
25: ///////////////////////
26:
27: return TRUE; // return TRUE unless you set the focus to a control
28: }
1: void CDay7Dlg::OnChangeEsamptext()
2: {
3: // TODO: If this is a RICHEDIT control, the control will not
4: // send this notification unless you override the ÂCDialog::OnInitialUpdate()
5: // function and call CRichEditCrtl().SetEventMask()
6: // with the EN_CHANGE flag ORed into the mask.
7:
8: // TODO: Add your control notification handler code here
9:
10: ///////////////////////
11: // MY CODE STARTS HERE
12: ///////////////////////
13:
14: // Update the variables with the dialog controls
15: UpdateData(TRUE);
16:
17: // Copy the current text to the font sample
18: m_strDisplayText = m_strSampText;
19:
20: // Update the dialog with the variables
21: UpdateData(FALSE);
22:
23: ///////////////////////
24: // MY CODE ENDS HERE
25: ///////////////////////
26: }
If you compile and run your application, you should be able to type text into the edit box and see it change in the font display area in the group box below.
Before you can start changing the font for the display area, you'll need to have a CFont member variable of the dialog class that you can use to set and change the display font. To add this variable, follow these steps:
When adding the code to use the selected font, you'll add it as a separate function that is not attached to a control. Why you do this will become clear as you proceed further through building and running today's application. To add the function to display and use the selected font, follow these steps:
1: void CDay7Dlg::SetMyFont()
2: {
3: CRect rRect; // The rectangle of the display area
4: int iHeight; // The height of the display area
5:
6: // Has a font been selected?
7: if (m_strFontName != "")
8: {
9: // Get the dimensions of the font sample display area
10: m_ctlDisplayText.GetWindowRect(&rRect);
11: // Calculate the area height
12: iHeight = rRect.top - rRect.bottom;
13: // Make sure the height is positive
14: if (iHeight < 0)
15: iHeight = 0 - iHeight;
16: // Release the current font
17: m_fSampFont.Detach();
18: // Create the font to be used
19: m_fSampFont.CreateFont((iHeight - 5), 0, 0, 0, FW_NORMAL,
20: 0, 0, 0, DEFAULT_CHARSET, OUT_CHARACTER_PRECIS,
21: CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH
22: FF_DONTCARE, m_strFontName);
23:
24: // Set the font for the sample display area
25: m_ctlDisplayText.SetFont(&m_fSampFont);
26: }
27: }
1: void CDay7Dlg::OnSelchangeLfonts()
2: {
3: // TODO: Add your control notification handler code here
4:
5: ///////////////////////
6: // MY CODE STARTS HERE
7: ///////////////////////
8: 9: // Update the variables with the dialog controls 10: UpdateData(TRUE); 11: 12: // Set the font for the sample 13: SetMyFont(); 14: 15: /////////////////////// 16: // MY CODE ENDS HERE 17: /////////////////////// 18: }
In the SetMyFont function, you first checked to make sure that a font had been selected. Next, you retrieved the area of the static text control that will be used to display the font. This enables you to specify a font height just slightly smaller than the height of the area you have available to display the font in. After you calculated the height of the static text control and made sure that it is a positive value, you created the selected font and told the static text control to use the newly created font.
In the OnSelchangeLfonts function, you copy the control values to the attached variables and then call the SetMyFont function to use the selected font. If you compile and run your application, you should be able to select a font and see it displayed in the sample static text control, as in Figure 7.3.
FIGURE 7.3. Displaying the selected font.
Today you learned how to use fonts in Visual C++ applications. You learned how to get a list of the available fonts that are loaded on the system and then how to create a font for use on a display object. You learned how you can create and use callback functions to get a list of resources from the Windows operating system. You also learned how you can access controls from the callback function using a window pointer that you passed to the function requesting the resource list.
1: void CDay7Dlg::SetMyFont()
2: {
3:
4: // Has a font been selected?
5: if (m_strFontName != "")
6: {
7: // Assume that the font size has already been initialized in the
8: // m_lLogFont structure. This allows you to only have to specify
9: // the font name.
10: tcscpy(m_lLogFont.lfFaceName, m_strFontName);
11: // Create the font to be used
12: m_fSampFont.CreateFontIndirect(&m_lLogFont);
13:
14: // Set the font for the sample display area
15: m_ctlDisplayText.SetFont(&m_fSampFont);
16: }
17: }
int CALLBACK EnumFontFamProc(LPENUMLOGFONT lpelf,
LPNEWTEXTMETRIC lpntm, DWORD nFontType, long lParam)
{
// Create a pointer to the dialog window
CDay7Dlg* pWnd = (CDay7Dlg*) lParam;
// Limit the list to TrueType fonts
if ((nFontType & TRUETYPE_FONTTYPE) == TRUETYPE_FONTTYPE)
{
// Add the font name to the list box
pWnd->m_ctlFontList.AddString(
lpelf->elfLogFont.lfFaceName);
}
// Return 1 to continue font enumeration
return 1;
}
The Workshop provides quiz questions to help you
FIGURE 7.4. Displaying the selected font with the font name.
FIGURE 7.5. Displaying the selected font in italics.
Well, you've made it through the first week. By this point, you've gotten a good taste for what's possible when building applications with Visual C++. Now it's time to look back over what's been covered and what you should have learned up to this point.
What you might want to do at this point, to
One of the most important things that you should understand at this point is how you can use controls and dialog windows in your applications to get and display information to the user. This is an important part of any Windows application because just about every application
Another key skill that you will be using in the majority of your applications is the ability to build and incorporate menus into your applications. You need to have a firm understanding of how to design a good menu, how to make sure that there are no conflicting
You will find that there are various situations in which you need to have some means of triggering actions on a regular basis or in which you need to keep track of how long some process has been running. For both of these situations, as well as numerous others, you'll often find yourself turning to the use of timers in your application. If you are even slightly foggy on how you can integrate timers into your applications, you will definitely want to go back and review Day 4.
Understanding how you can use text and fonts in your applications will allow you to build more flexibility into the appearance of your applications--to give your users the ability to customize the appearance as they want. You will be able to examine the available fonts on the computer on which your application is running and, if a font that you want to use in your application isn't available, choose another font that is close to use instead. If you still have any questions on how the font infrastructure in Windows works and how you can use it in your applications, you'll want to go back and review Day 7 once more.
Depending on the nature of your application, being able to capture and track mouse and keyboard actions by the user can be very important. If you are building a drawing application, this is crucial information. If you are building an application that needs to include drag-and-drop capabilities, this is important once again. There are any number of situations in which you'll want to include this functionality into your applications. By this point, you should understand how you can capture the various mouse events and determine which mouse
Finally, you should be familiar with the Visual C++ development environment, the Developer Studio. You should have a good understanding of what each area of the environment is for and how you can use the various tools and utilities in building your applications. You should be comfortable with using the workspace pane to navigate around your application project, locating and bringing into the various editors and designers any part of your application. You should be comfortable with locating and
By now you should be getting
Copyright, Macmillan Computer Publishing. All rights reserved.