CFontHelper






4.60/5 (9 votes)
Feb 10, 2004
9 min read

66344

2110
A Font Helper class to manage your view's fonts.
Introduction
We will adapt the view of an MFC application so that a user can select a font from a standard Windows dialog box, which will automatically update the contents of the view with the new font.
MFC Framework: Determining where Font Selection should be handled
First, we have to define a command handler for the ID_SELECTFONT
Options menu item. But what part of the MFC framework should be responsible for handling this command? As a rule of thumb, you should consider what to do for each application class, and choose the method that has the least work to do.
Here are four choices, sorted from worst to best:
- Application: it seems hard to be able to "touch" each view,
AfxGetMainWnd()
provides a pointer to the frame; - Frame window:
GetActiveView()
allows you to get a pointer to the active view only; - View: each view must duplicate the same code and, in addition, notify the other views;
- Document: easy to synchronize each view with
UpdateAllViews()
.
As you can see, the Document is the best candidate for this task. Once the command is received, the document will notify each attached view through the same mechanism, that is UpdateAllViews()
. So, add a OnSelectFont()
command handler to C???Doc
.
How to set Windows Fonts
Use WM_SETFONT and HFONT
As you are well aware, a font is a window attribute. When you want a window to change its display to a new font, you send a WM_SETFONT
message to it with an HFONT
as parameter. This works for every Windows common control, such as an edit field, a list view or a tree view.
Now that you know how to change a window font, we have to learn how to get a font, or to be more precise, an HFONT
. MFC provides a CFont
class which wraps an HFONT
, but we will manipulate HFONT
directly, since this will be easier for us. When you want a font, you need to declare it logically through a logical font, described in Windows as a LOGFONT
structure:
typedef struct tagLOGFONT { LONG lfHeight; LONG lfWidth; LONG lfEscapement; LONG lfOrientation; LONG lfWeight; BYTE lfItalic; BYTE lfUnderline; BYTE lfStrikeOut; BYTE lfCharSet; BYTE lfOutPrecision; BYTE lfClipPrecision; BYTE lfQuality; BYTE lfPitchAndFamily; TCHAR lfFaceName[LF_FACESIZE]; } LOGFONT;
Don't fret, you will not have to guess which value to set in each LOGFONT
member, Windows provides several Win32 API functions to help you. Once a font is logically defined, you ask Windows to give you back an HFONT
handle to a physical font which can be really used through the CreateFontIndirect()
Win32 function.
Standard Windows dialog for common font selection
Now we know how to transform a logical font into a real font that can be sent to a window, the question is how do we get the logical font? Among the set of common dialog boxes provided by Windows, you will find a standard "font picker" (or Font) dialog. If you call the Win32 API function ChooseFont()
, you get a logical font filled with the description of the font which is selected by the user from the standard Font dialog:
Here is its declaration:
BOOL ChooseFont(LPCHOOSEFONT lpcf);
If a font has been selected, it returns TRUE
and FALSE
otherwise. You have to define a CHOOSEFONT
structure before calling it:
typedef struct { DWORD lStructSize;// must be sizeof(CHOOSEFONT) HWND hwndOwner; // parent window of the displayed dialog HDC hDC; LPLOGFONT lpLogFont; INT iPointSize; DWORD Flags; // CF_XXX flags to customize the dialog behavior DWORD rgbColors; LPARAM lCustData; LPCFHOOKPROC lpfnHook; LPCTSTR lpTemplateName; HINSTANCE hInstance; LPTSTR lpszStyle; WORD nFontType; // font type WORD ___MISSING_ALIGNMENT__; INT nSizeMin; // only fonts larger than nSizeMin are selected INT nSizeMax; // only fonts smaller than nSizeMax are selected // CF_LIMITSIZE must be set to take nSizeMin/nSizeMax into account } CHOOSEFONT;
We are just interested in the lpLogFont
member which points to a LOGFONT
structure used to set the currently selected font when the dialog box is opened. (Flags must be set to the value CF_INITTOLOGFONTSTRUCT
). Once ChooseFont()
returns TRUE
, lpLogFont
contains the font’s logical description.
If you want to dig deeper into font manipulation, you should refer to the following Visual C++ samples: TTfont
s, FontView
and GridFont
.
Code Reusability: Writing a Font Helper Class
We will create a helper class to wrap font manipulation. First, we need to list each feature needed. Next, we will try to find out which parameter is required to initialize it. Finally, we will write a class on its own, to be integrated with C???Doc
.
Definition of CFontHelper
This class will wrap a font in order to help us handle logical and physical fonts. Here is what we would expect from such a class:
- Give access to its logical description and its corresponding font handle
- Save and load itself
- Display the Windows common font picker dialog and store the corresponding font
- Create a default font
To implement these features, some parameters are required. A section and several entry strings are needed to save and restore its font description. Also, we should allow different ways to create such an object:
- From scratch, and in this case a default font is created.
- From a description already saved under a section and entry.
Class creation
Now we have to turn the features listed above into code. Create a new class with the following declaration:
class CFontHelper : public CObject { // construction/destruction public: CFontHelper(); CFontHelper(LPCTSTR szSection, LPCTSTR szEntry); ~CFontHelper(); // public interface public: // 1. data access HFONT CreateFont(); LOGFONT GetLogFont(LOGFONT& LogFont); HFONT GetFontHandle(); // 2. save/restore void Save(); void Restore(); void SetSectionName(LPCTSTR szSection); void SetEntryName(LPCTSTR szEntry); // 3. Windows common font picker dialog BOOL ChooseFont(); // internal helpers protected: // 4. default font void DefaultFontInit(); void DefaultInit(); // internal members protected: HFONT m_hFont; LOGFONT m_LogFont; CString m_szSection; CString m_szEntry; };
We store both logical and physical definitions of a font with m_LogFont
and m_hFont
members. One of the tasks of the class is to keep both parameters consistent within its methods.
Implementation of CFontHelper
The CFontHelper
implementation is divided into four different parts: initialization, font picker dialog, save/restore definition, and logical/physical font management. Our class defines two constructors. The first is shown in this code sample:
// this constructor will automatically try to load the font // description from the INI/Registry CFontHelper::CFontHelper(LPCTSTR szSection, LPCTSTR szEntry) { // defensive programming ASSERT((szSection != NULL) && (_tcslen(szSection) > 0)); ASSERT((szEntry != NULL) && (_tcslen(szEntry) > 0)); m_szSection = szSection; m_szEntry = szEntry; // set default values DefaultFontInit(); // try to load the saved description Restore(); }
This constructor loads the font from a saved description under szSection
and szEntry
. It relies on both DefaultFontInit()
and Restore()
methods to retrieve the font description. Since save/restore parameters are provided, we only need to create a default font:
// set the logical font information: "Lucida Sans Unicode" size 8 void CFontHelper::DefaultFontInit() { // define the logical parameters for the default font m_LogFont.lfHeight = -11; // size 8 m_LogFont.lfWidth = 0; m_LogFont.lfEscapement = 0; m_LogFont.lfOrientation = 0; m_LogFont.lfWeight = FW_NORMAL; m_LogFont.lfItalic = 0; m_LogFont.lfUnderline = 0; m_LogFont.lfStrikeOut = 0; m_LogFont.lfCharSet = 0; m_LogFont.lfOutPrecision = OUT_STRING_PRECIS; m_LogFont.lfClipPrecision = CLIP_STROKE_PRECIS; m_LogFont.lfQuality = DEFAULT_QUALITY; m_LogFont.lfPitchAndFamily = FF_SWISS | VARIABLE_PITCH; _tcscpy(m_LogFont.lfFaceName, _T("Lucida Sans Unicode")); // create the associated font CreateFont(); }
You might wonder why we are creating a default font with CreateFont()
, since Restore()
will create a new one just a few milliseconds later. The reason is simple — error handling. If the given szSection
/szEntry
does not provide access to a valid font description, the CFontHelper
object would nevertheless be valid: it will contain a font, not the one expected but a default font.
The second constructor relies on DefaultInit()
to initialize its members with default values:
CFontHelper::CFontHelper()
{
// init with default values for INI/Registry and font description
DefaultInit();
}
And here is its helper function’s implementation:
// set the logical font information and INI/Registry access to // default values void CFontHelper::DefaultInit() { // set default font settings as "Lucida Sans Unicode" size 8 DefaultFontInit(); // default saving section/entry m_szSection = _T("Settings"); m_szEntry = _T("Font"); }
It creates the default font and sets save/restore keys with default values.
Windows common Font dialog picker
Once a CFontHelper
is initialized, you can ask it to display the Windows Font picker dialog:
BOOL CFontHelper::ChooseFont() { CHOOSEFONT choosefont; LOGFONT LogFont = m_LogFont; // fill in the data needed for the Windows common font dialog choosefont.lStructSize = sizeof(CHOOSEFONT); choosefont.hwndOwner = ::GetActiveWindow(); choosefont.hDC = 0; choosefont.lpLogFont = &LogFont; choosefont.iPointSize = 0; choosefont.Flags = CF_SCREENFONTS|CF_INITTOLOGFONTSTRUCT; choosefont.rgbColors = 0; choosefont.lCustData = 0; choosefont.lpfnHook = 0; choosefont.lpTemplateName = NULL; choosefont.hInstance = 0; choosefont.lpszStyle = 0; choosefont.nFontType = SCREEN_FONTTYPE; choosefont.nSizeMin = 0; choosefont.nSizeMax = 0; // use COMMDLG function to get new font selection from user if (::ChooseFont(&choosefont) != FALSE) { // keep track of the current font m_LogFont = LogFont; // create a Windows font according to the logical // information CreateFont(); return TRUE; } else return FALSE; }
This method prepares a call to the Win32 API ChooseFont()
function. The CHOOSEFONT
structure is therefore initialized to set the behavior we want:
- The Font dialog will be modal for the current active window (main frame in our case).
- The currently selected font will be the one described by
LogFont
. - The logical font description is copied back into a temporary variable
LogFont
. - Only screen fonts are presented by the dialog.
- If a font is selected, its logical description is saved in
m_LogFont
andCreateFont()
is called to create and save the corresponding font handle.
Management and lifetime of font description
The helper method used to transform a logical font into a real Windows font is called CreateFont()
:
// create the associated font HFONT CFontHelper::CreateFont() { HFONT hFont = ::CreateFontIndirect(&m_LogFont); if (hFont == NULL) { // GetLastError(); can be used to understand why the font was not created TRACE("Impossible to create font\n"); } // don't forget to delete the current font if (m_hFont != NULL) ::DeleteObject(m_hFont); // store the font (event if the creation has failed) m_hFont = hFont; return hFont; }
The implementation is straightforward and relies on the Win32 API CreateFontIndirect()
function. Once a logical font has been converted into a font handle, it is saved in m_hFont
. A font created by CreateFontIndirect()
must be freed in order to avoid a resource leak. Therefore, the previously stored font handle is deleted using DeleteObject()
before saving the new one.
Once CFontHelper
contains a valid font, you will want to use it. Two methods allow you to retrieve either a logical font or a font handle. Their implementation is obvious. They both return the member's value.
// return the logical font description for the wrapped font LOGFONT CFontHelper::GetLogFont(LOGFONT& LogFont) { LogFont = m_LogFont; } // return the wrapped font HFONT CFontHelper::GetFontHandle() { return m_hFont; }
When it is time to destruct, CFontHelper
does not forget to release its resources:
CFontHelper::~CFontHelper() { // don't forget to delete the current font if (m_hFont != NULL) ::DeleteObject(m_hFont); }
Therefore, you should take care of deleting CFontHelper
only when you are sure the font you could have retrieved from it (with GetFontHandle()
, for example) is no longer in use.
Saving and restoring CFontHelper descriptions
A CFontHelper
object is able to save itself into an INI file or into the Registry. Four methods are responsible for doing this special serialization.
First, the object must know where its description will be saved and two methods offer the means to set these backup parameters:
void CFontHelper::SetSectionName(LPCTSTR szSection) { // defensive programming ASSERT((szSection != NULL) && (_tcslen(szSection) > 0)); m_szSection = szSection; } void CFontHelper::SetEntryName(LPCTSTR szEntry) { // defensive programming ASSERT((szEntry != NULL) && (_tcslen(szEntry) > 0)); m_szEntry = szEntry; }
They both store section and entry values into their associated protected members m_szSection
and m_szEntry
.
Since a handle value has no meaning once saved, it is better to copy the font’s logical description.
void CFontHelper::Save() { // SetSectionName() must be called before this method is used // SetEntryName() must be called before this method is used ASSERT(m_szSection.GetLength() > 0); ASSERT(m_szEntry.GetLength() > 0); // save the logical font description CString strBuffer; strBuffer.Format("%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d", m_LogFont.lfHeight, m_LogFont.lfWidth, m_LogFont.lfEscapement, m_LogFont.lfOrientation, m_LogFont.lfWeight, m_LogFont.lfItalic, m_LogFont.lfUnderline, m_LogFont.lfStrikeOut, m_LogFont.lfCharSet, m_LogFont.lfOutPrecision, m_LogFont.lfClipPrecision, m_LogFont.lfQuality, m_LogFont.lfPitchAndFamily); AfxGetApp()->WriteProfileString (m_szSection, m_szEntry + _T("_Desc"), strBuffer); // save the font name AfxGetApp()->WriteProfileString (m_szSection, m_szEntry + _T("_Name"), m_LogFont.lfFaceName); }
Each m_LogFont
numerical member is concatenated into a long string which is saved under a different entry than the font name. For example, here is the INI file layout where a font is stored by default:
[Setting]
Font_Desc=-11:0:0:0:400:0:0:0:0:3:2:1:66
Font _Name=Comic Sans MS
Here is the implementation of what happens when a font needs to be reloaded:
// get the logical font from the INI/Registry void CFontHelper::Restore() { // SetSectionName() must be called before this method is used // SetEntryName() must be called before this method is used ASSERT(m_szSection.GetLength() > 0); ASSERT(m_szEntry.GetLength() > 0); // get font description from INI/Registry CString strBuffer=AfxGetApp()->GetProfileString(m_szSection, m_szEntry + _T("_Desc")); // nothing is saved // --> keep the current font if (strBuffer.IsEmpty()) return; LOGFONT LogFont; int cRead = _stscanf (strBuffer, "%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d", &LogFont.lfHeight, &LogFont.lfWidth, &LogFont.lfEscapement, &LogFont.lfOrientation, &LogFont.lfWeight, &LogFont.lfItalic, &LogFont.lfUnderline, &LogFont.lfStrikeOut, &LogFont.lfCharSet, &LogFont.lfOutPrecision, &LogFont.lfClipPrecision, &LogFont.lfQuality, &LogFont.lfPitchAndFamily); if (cRead != 13) { TRACE("Restore(): Invalid Registry/INI file entry\n"); return; } // get the font name strBuffer = AfxGetApp()->GetProfileString(m_szSection, m_szEntry + _T("_Name")); if (strBuffer.GetLength() <= 0) return; _tcscpy(LogFont.lfFaceName, strBuffer); // take into account the loaded logical font description m_LogFont = LogFont; // create the associated font CreateFont(); }
The logical font description is retrieved in two steps: the numerical values first and then font name. Finally, a real font is created using CreateFont()
which sets m_hFont
to this new font handle.
How to use CFontHelper
Even if the helper class has been defined and explained, it is always easier to understand it using an example. Let’s see how to integrate CFontHelper
into our MFC application.
First, include the following header file to ???Doc.h:
#include "FontHelper.h"
Creation and saving in C???Doc
A CFontHelper
object is stored by C???Doc
as a protected member:
protected:
CFontHelper* m_pFontHelper;
It is created in the document’s constructor:
C???Doc::C???Doc() { ... // create an instance of the font helper m_pFontHelper = new CFontHelper(_T("Settings"), _T("Font")); }
It is deleted in the document’s destructor as follows:
C???Doc::~C???Doc() { // get rid of the font helper after having saved it if (m_pFontHelper != NULL) { m_pFontHelper->Save(); delete m_pFontHelper; } }
As usual, we don't forget to save it before its deletion.
The role of the frame
It is the role of the frame to ask C???Doc
to set the font for its views, before it creates them. We know that OnCreateClient()
(which you may recall is a CMainFrame
method) is called to embed all views in the document. So, use Class Wizard to add this function to the main frame class. Copy the following code just before OnCreateClient()
returns:
// Once views are created, it is time to set their font if (pContext != NULL) { C???Doc* pDocument = (C???Doc*)pContext->m_pCurrentDoc; pDocument->NotifyDefaultFont(); } else TRACE("Impossible to get frame creation parameters"); // don't call the default MFC processing since the views are // already created // return CFrameWnd::OnCreateClient(lpcs, pContext); return TRUE; }
In ???Doc.h, declare this method as public
:
public: // font management void NotifyDefaultFont(); Add the following source code to ???Doc.cpp: void C???Doc::NotifyDefaultFont() { // defensive programming ASSERT(m_pFontHelper != NULL); // use the font helper to get the font if (m_pFontHelper != NULL) { // dispatch new font to each view SetCurrentFont(); } }
This is a wrapper around a synchronizing method called SetCurrentFont()
which will be introduced later.
Selecting a new font: OnSelectFont()
You may recall that the objective of this exercise was to allow the user to select a font, and we created a handler for the WM_SELFONT
message at the beginning of this section. We can now implement this function OnSelectFont()
:
void C???Doc::OnSelectFont() { // defensive programming ASSERT(m_pFontHelper != NULL); // use the font helper to ask the user for a new font if (m_pFontHelper != NULL) { if (m_pFontHelper->ChooseFont()) // dispatch new font to every views SetCurrentFont(); } }
Our document asks m_pFontHelper
to present a Windows common font dialog picker to the user. Once a font has been selected, it needs to be sent to every associated view.
Synchronizing a font among views: UpdateAllViews() and OnUpdate()
Our document knows that its views must use a new font, but how do we tell them? Once again, we will use UpdateAllViews()
with a new custom lHint
.
Therefore, SetCurrentFont()
relies on UpdateAllViews()
to dispatch the new font to all attached views. Add a public method SetCurrentFont( )
to the document class and then add its implementation as follows:
// change the current font for all views void C???Doc::SetCurrentFont() { // defensive programming ASSERT(m_pFontHelper != NULL); // send the font to every views if (m_pFontHelper != NULL) UpdateAllViews(NULL,2/*change font*/, (CObject*)m_pFontHelper->GetFontHandle()); }
The parameter received by each view in their OnUpdate()
method will be the font handle provided by our font helper. You should now use Class Wizard to add the OnUpdate( )
method to your view class. Now, you have to add a new view message to OnUpdate()
for each view:
// the current font has been changed by the user switch(lHint) { case 2: { // change font SendMessage(WM_SETFONT, (WPARAM)(HFONT)pHint, 0L); // redraw all Invalidate(TRUE); } break; }
The view sends a WM_SETFONT
message to itself and sets the input font handle as wParam
.
Additional Notes
For more information, please refer to Ivor Horton's Beginning Visual C++ by Wrox Press.