65.9K
CodeProject 正在变化。 阅读更多。
Home

CFontHelper

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.60/5 (9投票s)

2004年2月10日

9分钟阅读

viewsIcon

66971

downloadIcon

2110

一个字体助手类,用于管理你的视图字体。

Sample Image - FontHelper.jpg

引言

我们将修改 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 出版。

© . All rights reserved.