Click here to Skip to main content
15,888,610 members
Articles / Desktop Programming / MFC
Article

CFontHelper

Rate me:
Please Sign up or sign in to vote.
4.60/5 (9 votes)
9 Feb 20049 min read 65.8K   2.1K   29   6
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


Written By
Architect
United States United States
Ahmed had his M.S. Degree in Electrical and Computer Engineering in USA and the B.Sc. Degree in Automatic Control and Computer Systems Engineering in Egypt. He programmed with Assembly, Fortran77, Prolog, C/C++, Microsoft Visual Basic, Microsoft Visual C++, ATL/COM, Microsoft Visual C#, VB.NET, ASP.NET, AJAX, SharePoint 2007/2010, Microsoft Commerce Server, and MATLAB and Maple for technical computing. His programming experience is about 15+ years and he is a Microsoft Certified Professional. He also has a working experience in Database technologies and administration of SQL Server databases on Windows NT/2000/2003/2008 Windows Server platforms. Ahmed is interested in developing enterprise business solutions and has participated in many business, services and consultancy projects.

Comments and Discussions

 
GeneralNeed some help to get the font for selected text......... Pin
bhanu_codeproject22-Jul-07 21:47
bhanu_codeproject22-Jul-07 21:47 
GeneralRe: Need some help to get the font for selected text......... Pin
Ahmed Alhosaini23-Jul-07 3:35
Ahmed Alhosaini23-Jul-07 3:35 
Generalnice project but needs some work [modified] Pin
effem28-Apr-07 21:38
effem28-Apr-07 21:38 
GeneralRe: nice project but needs some work Pin
Ahmed Alhosaini29-Apr-07 9:30
Ahmed Alhosaini29-Apr-07 9:30 
GeneralCompile with MSVC8 Pin
FauthD25-Apr-07 7:12
FauthD25-Apr-07 7:12 
GeneralRe: Compile with MSVC8 Pin
Ahmed Alhosaini25-Apr-07 9:51
Ahmed Alhosaini25-Apr-07 9:51 
Hi,

Thanks for the tip. I will also try to update the class so that it can run under the new .NET 3.0 platform.

AYH

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.