CFontHelper






4.60/5 (9投票s)
2004年2月10日
9分钟阅读

66971

2110
一个字体助手类,用于管理你的视图字体。
引言
我们将修改 MFC 应用程序的视图,以便用户可以从标准的 Windows 对话框中选择字体,这将自动用新字体更新视图的内容。
MFC 框架:确定字体选择应在哪里处理
首先,我们必须为“Options”菜单项 `ID_SELECTFONT` 定义一个命令处理程序。但是,MFC 框架的哪一部分应该负责处理此命令呢?经验法则是,您应该考虑为每个应用程序类做什么,并选择工作量最少的方法。
以下是四种选择,按从差到好的顺序排列:
- 应用程序:似乎很难“接触”到每个视图,`AfxGetMainWnd()` 提供了一个指向框架的指针;
- 框架窗口:`GetActiveView()` 只允许您获取指向活动视图的指针;
- 视图:每个视图都必须复制相同的代码,并且此外,还需要通知其他视图;
- 文档:通过 `UpdateAllViews()` 轻松同步每个视图。
如您所见,文档是此任务的最佳选择。命令一旦接收,文档将通过相同的机制通知每个附加的视图,即 `UpdateAllViews()`。因此,为 `C???Doc` 添加一个 `OnSelectFont()` 命令处理程序。
如何设置 Windows 字体
使用 WM_SETFONT 和 HFONT
众所周知,字体是窗口属性。当您希望窗口更改其显示为新字体时,您会向其发送一个 `WM_SETFONT` 消息,并将 `HFONT` 作为参数。这适用于所有 Windows 通用控件,例如编辑字段、列表视图或树视图。
现在您知道如何更改窗口字体,我们必须学习如何获取字体,或者更确切地说,是 `HFONT`。MFC 提供了 `CFont` 类,它封装了 `HFONT`,但我们将直接操作 `HFONT`,因为这样对我们来说更简单。当您需要字体时,您需要通过逻辑字体在逻辑上声明它,在 Windows 中描述为 `LOGFONT` 结构。
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;
别担心,您不必猜测在每个 `LOGFONT` 成员中设置什么值,Windows 提供了几个 Win32 API 函数来帮助您。一旦逻辑定义了字体,您就可以通过 `CreateFontIndirect()` Win32 函数要求 Windows 返回一个指向物理字体的 `HFONT` 句柄。
标准的 Windows 对话框用于通用字体选择
现在我们知道如何将逻辑字体转换为真实的字体,可以发送给窗口,问题是如何获取逻辑字体?在 Windows 提供的通用对话框集中,您会找到一个标准的“字体选择器”(或字体)对话框。如果您调用 Win32 API 函数 `ChooseFont()`,您将获得一个逻辑字体,其中包含用户从标准字体对话框中选择的字体描述。
这是它的声明
BOOL ChooseFont(LPCHOOSEFONT lpcf);
如果选择了字体,它返回 `TRUE`,否则返回 `FALSE`。您必须在调用它之前定义一个 `CHOOSEFONT` 结构。
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;
我们只对 `lpLogFont` 成员感兴趣,它指向一个 `LOGFONT` 结构,用于在打开对话框时设置当前选择的字体。(标志必须设置为 `CF_INITTOLOGFONTSTRUCT` 的值)。一旦 `ChooseFont()` 返回 `TRUE`,`lpLogFont` 就包含字体的逻辑描述。
如果您想深入研究字体操作,可以参考以下 Visual C++ 示例:`TTfonts`、`FontView` 和 `GridFont`。
代码重用:编写一个字体助手类
我们将创建一个助手类来封装字体操作。首先,我们需要列出每个所需的功能。接下来,我们将尝试找出初始化它所需的参数。最后,我们将单独编写一个类,以便与 `C???Doc` 集成。
CFontHelper 的定义
此类将封装字体,以便帮助我们处理逻辑字体和物理字体。以下是我们对此类所期望的:
- 提供对其逻辑描述及其对应字体句柄的访问权限
- 保存和加载自身
- 显示 Windows 通用字体选择器对话框并存储相应的字体
- 创建默认字体
为了实现这些功能,需要一些参数。需要一个节和几个条目字符串来保存和恢复其字体描述。此外,我们应该允许以不同方式创建此类对象。
- 从头开始,在这种情况下会创建一个默认字体。
- 从已保存在某个节和条目下的描述加载。
类创建
现在我们必须将上面列出的功能转化为代码。创建一个具有以下声明的新类:
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; };
我们使用 `m_LogFont` 和 `m_hFont` 成员存储字体的逻辑和物理定义。该类的任务之一是在其方法中保持这两个参数的一致性。
CFontHelper 的实现
`CFontHelper` 的实现分为四个不同的部分:初始化、字体选择器对话框、保存/恢复定义以及逻辑/物理字体管理。我们的类定义了两个构造函数。第一个在以下代码示例中显示:
// 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(); }
此构造函数从 `szSection` 和 `szEntry` 下的已保存描述加载字体。它依赖于 `DefaultFontInit()` 和 `Restore()` 方法来检索字体描述。由于提供了保存/恢复参数,我们只需要创建一个默认字体。
// 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(); }
您可能会想,为什么我们使用 `CreateFont()` 创建默认字体,而 `Restore()` 几毫秒后就会创建一个新的。原因很简单——错误处理。如果给定的 `szSection`/`szEntry` 无法访问有效的字体描述,`CFontHelper` 对象仍然有效:它将包含一个字体,不是预期的字体,而是默认字体。
第二个构造函数依赖于 `DefaultInit()` 来用默认值初始化其成员。
CFontHelper::CFontHelper()
{
// init with default values for INI/Registry and font description
DefaultInit();
}
这是其辅助函数的实现:
// 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"); }
它创建默认字体并将保存/恢复键设置为默认值。
Windows 通用字体选择器对话框
初始化 `CFontHelper` 后,您可以要求它显示 Windows 字体选择器对话框。
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; }
此方法准备调用 Win32 API `ChooseFont()` 函数。因此,`CHOOSEFONT` 结构被初始化以设置我们想要的行为:
- 对于当前活动窗口(在本例中为主框架),字体对话框将是模态的。
- 当前选择的字体将是 `LogFont` 描述的字体。
- 逻辑字体描述被复制回临时变量 `LogFont`。
- 对话框仅显示屏幕字体。
- 如果选择了字体,其逻辑描述将被保存在 `m_LogFont` 中,并调用 `CreateFont()` 来创建并保存相应的字体句柄。
字体描述的管理和生命周期
将逻辑字体转换为真实 Windows 字体的辅助方法称为 `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; }
实现很简单,依赖于 Win32 API `CreateFontIndirect()` 函数。一旦逻辑字体转换为字体句柄,它就被保存在 `m_hFont` 中。通过 `CreateFontIndirect()` 创建的字体必须被释放以避免资源泄漏。因此,之前存储的字体句柄在保存新字体之前使用 `DeleteObject()` 删除。
一旦 `CFontHelper` 包含一个有效字体,您将希望使用它。两种方法允许您检索逻辑字体或字体句柄。它们的实现很明显。它们都返回成员的值。
// 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; }
在析构时,`CFontHelper` 不会忘记释放其资源。
CFontHelper::~CFontHelper() { // don't forget to delete the current font if (m_hFont != NULL) ::DeleteObject(m_hFont); }
因此,您应该注意仅在确定可以从 `CFontHelper`(例如,使用 `GetFontHandle()`)检索到的字体不再使用时才删除 `CFontHelper`。
保存和恢复 CFontHelper 描述
`CFontHelper` 对象能够将自身保存到 INI 文件或注册表中。四个方法负责执行此特殊序列化。
首先,对象必须知道其描述将保存在何处,并且有两个方法提供了设置这些备份参数的方法。
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; }
它们都将节和条目值存储到其关联的受保护成员 `m_szSection` 和 `m_szEntry` 中。
由于句柄值在保存后没有意义,因此最好复制字体的逻辑描述。
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); }
每个 `m_LogFont` 数值成员都连接成一个长字符串,该字符串保存在与字体名称不同的条目下。例如,这是默认情况下存储字体的 INI 文件布局:
[Setting]
Font_Desc=-11:0:0:0:400:0:0:0:0:3:2:1:66
Font _Name=Comic Sans MS
以下是字体需要重新加载时发生情况的实现:
// 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(); }
逻辑字体描述分两步检索:首先是数值,然后是字体名称。最后,使用 `CreateFont()` 创建一个真实字体,该函数将 `m_hFont` 设置为此新字体句柄。
如何使用 CFontHelper
即使已经定义并解释了助手类,通过示例来理解它总是更容易的。让我们看看如何将 `CFontHelper` 集成到我们的 MFC 应用程序中。
首先,在 `???Doc.h` 中包含以下头文件:
#include "FontHelper.h"
在 C???Doc 中创建和保存
`CFontHelper` 对象由 `C???Doc` 存储为受保护成员。
protected:
CFontHelper* m_pFontHelper;
它在文档的构造函数中创建。
C???Doc::C???Doc() { ... // create an instance of the font helper m_pFontHelper = new CFontHelper(_T("Settings"), _T("Font")); }
它在文档的析构函数中按如下方式删除:
C???Doc::~C???Doc() { // get rid of the font helper after having saved it if (m_pFontHelper != NULL) { m_pFontHelper->Save(); delete m_pFontHelper; } }
照常,我们不会忘记在删除它之前保存它。
框架的作用
框架的职责是在创建其视图之前,要求 `C???Doc` 为其视图设置字体。我们知道 `OnCreateClient()`(您可能还记得它是 `CMainFrame` 的一个方法)在嵌入文档中的所有视图时被调用。因此,使用类向导将此函数添加到主框架类中。在 `OnCreateClient()` 返回之前复制以下代码:
// 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; }
在 `???Doc.h` 中,将此方法声明为 `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(); } }
这是对稍后将介绍的同步方法 `SetCurrentFont()` 的封装。
选择新字体:OnSelectFont()
您可能还记得,本次练习的目标是允许用户选择字体,并且我们在本节开头创建了一个 `WM_SELFONT` 消息的处理程序。我们现在可以实现 `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(); } }
我们的文档要求 `m_pFontHelper` 向用户显示一个 Windows 通用字体选择器对话框。一旦选择了字体,就需要将其发送到每个关联的视图。
视图之间的字体同步:UpdateAllViews() 和 OnUpdate()
我们的文档知道它的视图必须使用新字体,但我们如何告诉它们呢?再次,我们将使用 `UpdateAllViews()` 并使用一个新的自定义 `lHint`。
因此,`SetCurrentFont()` 依赖于 `UpdateAllViews()` 来将新字体分发到所有附加视图。将一个公共方法 `SetCurrentFont()` 添加到文档类,然后添加其实现如下:
// 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()); }
每个视图在其 `OnUpdate()` 方法中接收的参数将是我们的字体助手提供的字体句柄。您现在应该使用类向导将 `OnUpdate()` 方法添加到您的视图类中。现在,您必须为每个视图向 `OnUpdate()` 添加一个新的视图消息。
// 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; }
视图向自身发送 `WM_SETFONT` 消息,并将输入字体句柄设置为 `wParam`。
其他说明
有关更多信息,请参阅 Ivor Horton 的 *Beginning Visual C++*,由 Wrox Press 出版。