Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

CFontHelper

0.00/5 (No votes)
9 Feb 2004 2  
A Font Helper class to manage your view's fonts.

Sample Image - FontHelper.jpg

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: TTfonts, 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 and CreateFont() 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here