使 CMFCLinkCtrl 类更具开发友好性






4.86/5 (18投票s)
在 MFC 超链接控件中启用字体和格式更改。

引言
在过去的几年里,我几乎一直用 C# 编程,但最近我被要求用原生 C++ 编写一个应用程序,其中包含一些对话框中的超链接控件。该应用程序将在 Windows XP 或更高版本上运行。由于我大部分 C++ 经验都与 MFC 相关,并且我现在使用的是 Visual Studio 2010,因此使用新的 CMFCLinkCtrl [^] 来实现超链接似乎很自然。该控件作为 Visual C++ 2008 Feature Pack 的一部分推出,然后在 Visual Studio 2010 中成为 MFC 的常规组成部分。
当我开始使用 CMFCLinkCtrl 时,我意识到它的限制将使默认实现无法满足我的应用程序需求。这些限制中最主要的是字体类型、字号和超链接文本颜色是固定的。所谓“固定”,我的意思是 硬编码。好吧,不是完全硬编码,但非常接近;在 CMFCLinkCtrl::OnDraw
中,超链接字体初始化如下:
pDC->SelectObject(&afxGlobalData.fontDefaultGUIUnderline);
反过来,afxGlobalData.fontDefaultGUIUnderline
由 MFC 框架在 MFC 应用程序启动时随所有 afxGlobalData
值一起初始化,它派生自 stock 对象 DEFAULT_GUI_FONT
,该对象通常指向 MS Shell Dlg 逻辑字体。我稍后会对此做更多说明,但重要的是,此字体可能与您 GUI 的其余部分完全不匹配。更改它的一个方法是修改您应用程序的 afxGlobalData.fontDefaultGUIUnderline
,我将在本文中向您展示如何操作。但是,更改此全局值的缺点是它将以完全相同的方式影响应用程序中 CMFCLinkCtrl 的每个实例。在某些情况下,这可能正是您想要的,但我通常更喜欢能够为每个超链接单独指定字体的灵活性。我的意思是字体类型和大小,以及我可能想要修改的其他属性,例如超链接是否带下划线以及是否使用斜体或粗体文本。我也喜欢能够让超链接自动使用对话框字体,这种做法涵盖了我应用程序中可能使用的四分之三的超链接。与 CMFCLinkCtrl 派生的类(本文提供的类)一样,我巧妙地将其命名为 CHyperlink,它实现了所有这些以及更多功能。
背景
CMFCLinkCtrl 类最初随 Visual C++ 2008 Feature Pack [^] 一起推出,现在与 Visual Studio 2010 一起分发,是 Microsoft 最新的基于 MFC 的超链接控件。与自 7.0 版(随 Visual Studio 2002 发布)以来一直是 MFC 的一部分的 CLinkCtrl [^] 不同,它只是 Win32 SysLink 控件 [^] 的一个简单包装器,CMFCLinkCtrl 是专门编写的 MFC 超链接控件。以前,如果您想要一个高级超链接控件,您必须自己构建一个,并且多年来 MFC 已经开发了几个知名的超链接类,包括 Chris Maunder 开发的原始 CodeProject 超链接控件 [^],后来由 Giancarlo Iovino 在 CodeGuru [^] 以及 Hans Dietrich 在 CodeProject [^] 上进行了扩展。
这三个控件以及同期开发的其他几个控件都源自 1997 年 12 月的《Microsoft Systems Journal》(现为《MSDN Magazine》)上 Paul DiLascia 发表的 CStaticLink 控件 [^]。一个例外是 Nguyen Duc Thanh 的 CURLLinkButton 类 [^],它派生自 CButton 而不是 CStatic。同样,新的 CMFCLinkCtrl 类与大多数以前的 VC++ 超链接控件不同,它继承自 CMFCButton,而 CMFCButton 是 CButton 的派生类。与 CMFCLinkCtrl 一样,CMFCButton 类也是 Microsoft 作为 Visual C++ 2008 Feature Pack 的一部分集成到 MFC 中的,并且现在随 Visual Studio 2010 一起分发。
玩转 CMFCLinkCtrl
当我第一次在 Visual Studio 2010 设计器的工具箱中看到标有 MFC Link Control 的项目时,我以为它对应一个类,该类将包含我们通常与超链接控件关联的所有典型属性:字体类型和大小;超文本颜色(普通、悬停和已访问);当然还有一种设置 URL 和控件显示文本的方法。令我惊讶的是,字体和文本颜色基本上是固定的,这至少是我希望修改的内容,以便它们能与我 GUI 的其余部分协调。通常我希望超链接带下划线,但偶尔也有我想关闭下划线的情况。查看 Microsoft 的类 API 文档 [^],在我看来,您只能做:
- 设置 URL。
- 如果出于某种原因您不想在 URL 本身中包含 URL 前缀(例如 mailto: 或 http:),则可以选择设置它。
- 强制超链接的焦点矩形的大小与超链接的显示文本匹配。
另外,当然,由于该类通过 CButton 和 CMFCButton 派生自 CWnd,您可以使用 CWnd 的 SetWindowText()
成员函数来设置显示文本。这在 Visual Studio 设计器的属性窗口中显示为超链接的“Caption”。并且 CMFCLinkCtrl 从 CMFCButton 继承了一个 SetTooltip()
成员函数,让您可以轻松地为超链接设置工具提示文本。与 caption 一样,这可以通过编程方式设置,也可以在控件的 Visual Studio 设计器属性窗口中声明式地设置。
除了这些属性之外,CMFCLinkCtrl 还允许您通过公共成员变量 m_bAlwaysUnderlineText
为类的每个实例设置一个“始终带下划线”的属性,尽管此功能未在 Microsoft 的类 API 文档中提及。当 m_bAlwaysUnderlineText == FALSE
时,超链接仅在鼠标悬停在其上方时才带下划线。默认设置为 TRUE,为您提供一个始终带下划线的超链接。我有点惊讶地看到一个公共成员变量用于一个没有属性设置器和getter的重要设置。在主要使用 C# 编程了几年之后,直接使用公共成员变量感觉太二十世纪了。也许这就是为什么 Microsoft 的类 API 文档没有提及它的原因。
但对我来说,CMFCLinkCtrl 真正的决定性问题是它的字体基本上是固定的。要克服这样的限制,过去我会在我的 GUI 窗口类的代码中实现 OnCtlColor,并在那里更改与每个超链接对象相关的字体属性。对于 CMFCLinkCtrl,由于字体在 CMFCLinkCtrl::OnDraw
中是硬编码的,因此这不起作用。如果字体赋值是在类构造函数而不是 OnDraw
中完成的,那么在 OnCtlColor 中更改字体会足够简单。
即便如此,仍然有一种方法可以克服字体限制,而无需从 CMFCLinkCtrl 派生新类:在您的 GUI 窗口类中,您可以将以下代码放在 OnInitDialog
或等效函数(例如 OnInitialUpdate
或 OnShowWindow
)中。以下示例显示,默认的超链接字体将被 10 磅的 Segoe UI 字体(带下划线)替换。
// ---------------------------------------------------
// Declare a logfont object and then clear its memory.
// ---------------------------------------------------
LOGFONT lf;
memset(&lf, 0, sizeof(LOGFONT));
// ------------------------------------------------------------------------
// Specify a Segoe UI 10-point font, not italic, normal weight, underlined.
// ------------------------------------------------------------------------
_tcscpy_s(lf.lfFaceName, LF_FACESIZE, _T("Segoe UI"));
lf.lfHeight = 100;
lf.lfItalic = (BYTE)FALSE;
lf.lfWeight = FW_NORMAL;
lf.lfUnderline = (BYTE)TRUE;
// -----------------------------------------------------------------------
// Use the font to replace the afxGlobalData.fontDefaultGUIUnderline font.
// -----------------------------------------------------------------------
CClientDC dc(this);
afxGlobalData.fontDefaultGUIUnderline.Detach();
VERIFY(afxGlobalData.fontDefaultGUIUnderline.CreatePointFontIndirect(&lf, &dc));
如引言中所述,这效果很好,但其副作用是 CMFCLinkCtrl 的所有实例都将具有新字体。如果这正是您想要的,那么您就完成了。但是,如果您希望能够为每个超链接控件的实例指定单独的字体,请继续阅读。
派生的 CHyperlink 类
由于 CMFCLinkCtrl 类施加的限制,我决定从 CMFCLinkCtrl 派生一个新类,并添加我认为缺失的功能。要将派生的类 CHyperlink
集成到您的代码中,请将以下文件添加到您的项目中。这些文件包含在 CHyperlink
源代码文件的下载中,也包含在完整演示项目源代码文件的下载中。
- CHyperlink.cpp
- CHyperlink.h
然后,在您想要实例化 CHyperlink
类对象的任何类中添加以下行:
#include "CHyperlink.h"
在 Visual Studio 设计器中,对于将包含超链接的任何窗口,请从工具箱中选择 MFC Link Control,然后将其拖到窗口中您想要的位置。在属性窗口中,您可以设置 caption(显示文本)、tooltip、URL 和 URL 前缀(如果您想单独处理前缀而不是包含在 URL 中)。您也可以通过编程方式设置这些属性。现在,使用类向导创建一个与 MFC Link Control 对象的 IDC_xxx 标识符关联的控件成员变量。选择 CMFCLinkCtrl 作为成员变量的类型(它可能是唯一显示的选项)。完成此操作后,打开您的窗口类的头文件,找到您刚刚创建的成员变量,并将它的声明类型从 CMFCLinkCtrl 重命名为 CHyperlink。瞧:您的窗口类中现在有一个 CHyperlink 对象。
CHyperlink API
下表总结了 CHyperlink 的 API,包括从更高级别类继承的一些重要功能。属性设置器首先列出,然后是属性getter,然后是实用函数。
公共成员函数 | 描述 |
---|---|
void SetHyperlinkColors(COLORREF colorNormalLink, COLORREF colorHoverLink, COLORREF colorVisitedLink) | 设置三个超链接颜色
|
void SetHyperlinkFont(LPCTSTR strFontName, long nFontHeight, BOOL bItalics = FALSE, BOOL bBold = FALSE) | 根据字体类型名称、Windows LOGFONT 字符高度以及超链接是否应以斜体和/或粗体显示来设置超链接字体。 |
void SetHyperlinkFont(LPCTSTR strFontName, int nPoints, BOOL bItalics = FALSE, BOOL bBold = FALSE) | 根据字体类型名称、以磅为单位的字符高度以及超链接是否应以斜体和/或粗体显示来设置超链接字体。 |
void SetHyperlinkFontToDefault() | 将超链接字体设置为 CMFCLinkCtrl 的默认字体。这是 afxGlobalData.fontDefaultGUIUnderline 指定的字体,通常指向逻辑字体 MS Shell Dlg。但是,可以通过以编程方式将不同的字体选择到框架全局变量 afxGlobalData.fontDefaultGUIUnderline 中来更改默认字体。有关如何执行此操作的信息,请参阅上面标题为“玩转 CMFCLinkCtrl”的部分。 |
BOOL SetHyperlinkStyle(HyperlinkStyle enumValue) | 将超链接样式设置为四种枚举值之一,这些值描述了从祖先 CMFCButton 类继承的样式选项。
|
void SetMatchParentFont(BOOL bMatch) | 如果 bMatch 为 TRUE,则超链接字体将在窗口首次打开时匹配父窗口的字体。如果 bMatch 为 FALSE,则超链接最初将设置为父 CMFCLinkCtrl 类指定的默认字体。此函数旨在在窗口首次打开之前使用,通常在 OnInitDialog 或等效函数中。窗口打开后,字体可以随时通过编程方式更改。 如果未调用此函数,则默认假定应匹配父窗口的字体;即,CHyperlink 的行为如同调用了此函数并将参数设置为 TRUE。 |
BOOL SetTextAlignment(HyperlinkAlignment enumValue) | 将超链接的文本对齐方式设置为三种枚举值之一,这些值描述了从祖先 CMFCButton 类继承的对齐选项:Left、Right 或 Center。如果未调用此函数时使用的默认对齐方式是 Left 对齐。 如果对齐方式设置正确,则返回 TRUE;否则返回 FALSE。 |
void SetTooltip(LPCTSTR lpszToolTipText) | 从 CMFCLinkCtrl 继承。使用字符串参数设置超链接的工具提示文本。 |
void SetURL(LPCTSTR lpszURL) | 从 CMFCLinkCtrl 继承。使用字符串参数设置超链接的 URL。 |
void SetURLPrefix(LPCTSTR lpszPrefix) | 从 CMFCLinkCtrl 继承。使用字符串参数设置 URL 前缀,例如 http: 或 mailto: 。前缀也可以包含在 URL 本身中(请参阅 SetURL 函数)。显然,您不能同时在两个地方包含前缀;如果您已将其包含在 URL 中,请勿调用此函数。 |
void SetWindowText(LPCTSTR lpszString) | 从 CWnd 继承。将超链接的“caption”或显示文本设置为字符串参数的值。 |
void UnderlineHyperlink(BOOL bUnderline = TRUE) | 设置一个属性,该属性决定超链接是否始终带下划线(bUnderline 为 TRUE)或仅在鼠标悬停在超链接上时带下划线(bUnderline 为 FALSE)。默认情况下,超链接始终带下划线,因此如果这是期望的行为,则无需调用此函数。 |
CString GetHyperlinkFontName() | 返回一个 CString,其中包含当前用于超链接的字体的名称。如果名称是 MS Shell Dlg 或 MS Shell Dlg 2 之一的逻辑字体名称,则该函数会尝试通过查询 Windows 注册表来获取用户计算机上安装的相应实际字体的名称。如果成功,则返回该字体名称;否则返回逻辑字体的名称。 |
int GetHyperlinkFontPoints() | 返回一个整数,表示当前用于超链接的字的高度(以磅为单位)。 |
BOOL GetMatchParentFont() | 如果初始字体已设置为匹配父窗口的字体,则返回 TRUE。这可以通过在窗口首次打开之前显式调用 SetMatchParentFont(TRUE) 来实现,或者通过根本不调用 SetMatchParentFont() 来实现,因为默认情况下假定超链接字体应匹配父窗口的字体。 如果调用了 SetMatchParentFont(FALSE),则返回 FALSE,这将导致在窗口首次打开时使用 CMFCLinkCtrl 默认字体。 |
BOOL IsHyperlinkFontItalic() | 如果当前用于超链接的字体具有斜体字体,则返回 TRUE;否则返回 FALSE。 |
BOOL IsHyperlinkFontBold() | 如果当前用于超链接的字体具有 LOGFONT lfWeight 值等于或大于 FW_BOLD,则返回 TRUE;否则返回 FALSE。 |
CSize SizeToContent(BOOL bVCenter = FALSE, BOOL bHCenter = False) | 调整超链接的焦点矩形以适应超链接的显示文本。在文本和焦点矩形之间使用六个像素的填充。如果 bVCenter 为 TRUE,则显示文本将在超链接控件的顶部和底部之间垂直居中;默认值为 FALSE。如果 bHCenter 为 TRUE,则显示文本将在超链接控件的左侧和右侧之间水平居中;默认值为 FALSE。通常 bHCenter 参数可以保留其默认值,因为如果需要将文本对齐在控件的左右边距内,则可以使用 SetTextAlignment() 函数。 返回值是一个 CSize 对象,包含焦点矩形以像素为单位的宽度和高度。 |
CHyperlink 有两个公共枚举。尽管父类 CMFCLinkCtrl 及其父类 CMFCButton 之间共有相当多的公共成员变量,但 CHyperlink 为其中大多数变量提供了属性设置器和getter,如上表所示,因此变量本身在此处未被记录。如果您有理由直接使用公共成员变量,您需要查阅这些类的文档。一些公共成员变量未在 MSDN 中记录,因此您需要查阅源代码,您可以在 Visual Studio 2010 MFC 类的源代码文件夹中找到它们。该文件夹应位于 %ProgramFiles%\Microsoft Visual Studio 10.0\VC\atlmfc\src\mfc。两个父类的文件名为 afxlinkctrl.cpp 和 afxbutton.cpp。相应的头文件位于 %ProgramFiles%\Microsoft Visual Studio 10.0\VC\atlmfc\include。
公共枚举 | 描述 |
---|---|
enum HyperlinkStyle { TextOnly, ThreeDButton, FlatButton, SemiFlatButton }; | 此枚举有四个值,如左侧所示,并在上面的 SetHyperlinkStyle 表中进行了描述。要在程序中使用枚举,您不需要引用枚举变量本身,而是引用四个值之一;例如,如果您实例化了一个 CHyperlink 对象并将其与名为 m_ctlLINK 的控件成员变量关联,您可以使用枚举值来设置超链接样式,如下所示:m_ctlLINK.SetHyperlinkStyle(m_ctlLINK.ThreeDButton); |
enum HyperlinkAlignment { Left, Right, Center }; | 此枚举有三个值,如左侧所示,并在上面的 SetTextAlignment 表中进行了描述。要在程序中使用枚举,您不需要引用枚举变量本身,而是引用三个值之一;例如,如果您实例化了一个 CHyperlink 对象并将其与名为 m_ctlLINK 的控件成员变量关联,您可以使用枚举值来设置超链接对齐方式,如下所示:m_ctlLINK.SetTextAlignment(m_ctlLINK.Center); |
编写 CHyperlink 代码
本节探讨 CHyperlink 类的一些编程方面,因此是时候看一些代码了。首先,类构造函数将几个成员变量设置为默认值,并将初始超链接字体设置为与 CMFCLinkCtrl 相同的默认字体。这里的区别在于,我们在构造函数中而不是在 OnDraw
中完成它,因此可以轻松更改。类析构函数仅删除 CHyperlink 使用的两个字体对象。
// Constructor
CHyperlink::CHyperlink()
{
m_bHLinkShown = FALSE; // Becomes TRUE after the control has been shown the first time.
m_pParent = NULL; // Becomes non-NULL after the parent window exists.
m_bMatchParentFont = TRUE; // Assume the user wants to initially match the parent font.
m_nAlignStyle = ALIGN_LEFT; // Assume the user wants to align the text on the left.
// -------------------------------------
// Set default colors for the hyperlink.
// -------------------------------------
m_colorNormalLink = afxGlobalData.clrHotLinkNormalText;
m_colorHoverLink = afxGlobalData.clrHotLinkHoveredText;
m_colorVisitedLink = afxGlobalData.clrHotLinkVisitedText;
// ----------------------------------------------------------------------------------------
// Initially set the default underlined GUI font as the hyperlink font. This normally uses
// a Microsoft "logical" font, MS Shell Dlg. The first time OnDraw() is called we'll change
// this to the parent window's font but we don't know it yet so we can't do that now.
// ----------------------------------------------------------------------------------------
LOGFONT lf;
afxGlobalData.fontDefaultGUIUnderline.GetLogFont(&lf);
BOOL bBold = lf.lfWeight > FW_NORMAL ? TRUE : FALSE;
SetHyperlinkFont(lf.lfFaceName, lf.lfHeight, lf.lfItalic, bBold);
}
// Destructor
CHyperlink::~CHyperlink()
{
m_Font.DeleteObject(); // Delete the normal font.
m_ULFont.DeleteObject(); // Delete the underlined font.
}
请注意,构造函数调用了 API 表中描述的两个 SetHyperlinkFont
重载之一来创建默认字体。还要注意,构造函数设置了 m_bMatchParentFont = TRUE
。这意味着超链接的默认操作是使用与父窗口相同的字体。但是,在调用构造函数时,无法保证我们可以确定父窗口的字体,因为窗口可能尚未存在。因此,我们暂时将字体设置为 afxGlobalData.fontDefaultGUIUnderline
指定的任何值。第一次调用 OnDraw
时,我们将获取父窗口的字体并基于它设置超链接字体。也就是说,除非父程序先前调用了 CHyperlink::SetMatchParentFont(FALSE)
;在这种情况下,我们将字体保留为调用程序可能已指定的任何值。如果父程序调用 CHyperlink::SetMatchParentFont(FALSE)
而未指定字体,我们将字体保留在构造函数中设置的默认值。这与使用 CMFCLinkCtrl 而不是 CHyperlink 的效果相同,只是后一种情况您可以选择以后更改字体或其颜色和其他属性。
实现 OnDraw
下面是 OnDraw
的代码。请注意,除了设置字体之外,我们还在绘制文本之前设置了超链接的文本颜色。颜色值的使用取决于鼠标当前是否悬停在超链接上以及链接是否已被访问过。这些值可以通过 SetHyperlinkColors
API 函数由调用程序设置。类似地,调用程序可能通过调用 SetTextAlignment
API 函数更改了文本对齐方式。在调用 DrawText
之前的 switch
语句用于根据父程序可能指定的任何内容设置对齐方式。
void CHyperlink::OnDraw(CDC* pDC, const CRect& rect, UINT uiState)
{
ASSERT_VALID(pDC);
m_pParent = GetParent();
// ---------------------------------------------------------------------------
// Font initialization--do this exactly once, the first time OnDraw is called.
// ---------------------------------------------------------------------------
if (!m_bHLinkShown)
{
// ----------------------------------------------------------------------------
// If m_bMatchParentFont has been set, inherit the font from the parent window.
// ----------------------------------------------------------------------------
if (m_bMatchParentFont)
{
if (m_pParent != NULL)
{
LOGFONT lf;
m_pParent->GetFont()->GetLogFont(&lf); // Get the dialog font.
BOOL bBold = lf.lfWeight > FW_NORMAL ? TRUE : FALSE;
SetHyperlinkFont( // Set the font for the hyperlink:
lf.lfFaceName, // ... Name of the dialog font.
lf.lfHeight, // ... Font height from the parent.
(BOOL)lf.lfItalic, // ... Italics if used in parent.
bBold); // ... Bold if parent has bold.
}
}
m_bHLinkShown = TRUE; // Don't do this again.
}
// ------------------------
// Set the font to be used.
// ------------------------
CFont* pOldFont = NULL;
if (m_bAlwaysUnderlineText || m_bHover)
{
if (&m_Font != NULL)
{
// ----------------------------------------------------------
// Use the font set by the user with underlining added to it.
// ----------------------------------------------------------
if (!m_bULCreated)
{
CreateUnderlinedFont(&m_Font);
}
pOldFont = pDC->SelectObject(&m_ULFont);
}
else
{
// ------------------------------------------------------------
// No font set by the user, so use the default underlined font.
// ------------------------------------------------------------
pOldFont = pDC->SelectObject(&afxGlobalData.fontDefaultGUIUnderline);
}
}
else
{
// --------------------
// Not using underline.
// --------------------
if (&m_Font != NULL)
{
// -----------------------------
// Use the font set by the user.
// -----------------------------
pOldFont = pDC->SelectObject(&m_Font);
}
else
{
// --------------------------------------------------------
// No font set by the user, so use the default button font.
// --------------------------------------------------------
pOldFont = CMFCButton::SelectFont(pDC);
}
}
// -------------------------------
// Set the hyperlink's text color.
// -------------------------------
pDC->SetTextColor(m_bHover ? m_colorHoverLink : (m_bVisited ? m_colorVisitedLink : m_colorNormalLink));
// -----------------------------------------------------------------------------------
// Set the background mode to TRANSPARENT so the background will show behind the text.
// -----------------------------------------------------------------------------------
pDC->SetBkMode(TRANSPARENT);
// -----------------------------
// Get the text for the control.
// -----------------------------
CString strLabel;
GetWindowText(strLabel);
// --------------
// Draw the text.
// --------------
CRect rectText = rect;
if (m_bMultilineText)
{
pDC->DrawText(strLabel, rectText, DT_WORDBREAK | DT_TOP);
}
else
{
UINT nFormat = DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_VCENTER;
switch(m_nAlignStyle)
{
case ALIGN_CENTER: nFormat |= DT_CENTER; break;
case ALIGN_RIGHT: nFormat |= DT_RIGHT; break;
case ALIGN_LEFT:
default:
nFormat |= DT_LEFT;
break;
}
pDC->DrawText(strLabel, rectText, nFormat);
}
pDC->SelectObject(pOldFont);
}
我想在上面的代码中指出的一点是,我没有花太多时间去纠结多行超链接,因为我不认为我曾经使用过它们。在上面的代码中,我基本上做了 CMFCLinkCtrl
对多行超链接所做的事情,这并没有什么特别的。如果多行超链接对您很重要,那么您可能需要花一些时间修改 OnDraw
的该部分以更全面地支持它们。
处理逻辑字体
CHyperlink.cpp
中的大部分代码都非常直接且注释良好,并且由于我将其归类为中级文章,所以我不会花太多时间讨论大多数 MFC 程序员已经熟悉的代码。属性设置器和getter中的任何内容都不应该有什么不寻常之处。但是,我会简要介绍 GetHyperlinkFontName
函数,因为它需要对 MS Shell Dlg 和 MS Shell Dlg 2 逻辑字体进行特殊处理。这些逻辑字体在我开始查看 CMFCLinkCtrl 代码时是我的新知识,所以它们也可能对其他人来说是新的。这是 GetHyperlinkFontName
的代码:
CString CHyperlink::GetHyperlinkFontName()
{
CString strLogName;
LOGFONT lf;
memset(&lf, 0, sizeof(LOGFONT));
if (&m_Font != NULL)
{
m_Font.GetLogFont(&lf);
}
else
{
afxGlobalData.fontDefaultGUIUnderline.GetLogFont(&lf);
}
strLogName = lf.lfFaceName;
if ((strLogName == _T("MS Shell Dlg")) || (strLogName == _T("MS Shell Dlg 2")))
{
// ------------------------------------------------------------------------------------------------
// The font is one of Microsoft's two logical fonts, so get the actual font name from the registry.
// ------------------------------------------------------------------------------------------------
CRegKey regKey;
CString strKey = _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\FontSubstitutes\\");
if (regKey.Open(HKEY_LOCAL_MACHINE, strKey, KEY_QUERY_VALUE) == ERROR_SUCCESS)
{
TCHAR szName[256];
ULONG nChars = 256;
if (regKey.QueryStringValue((LPCTSTR)strLogName, szName, &nChars) == ERROR_SUCCESS)
{
int nLen = _tcslen(szName);
// -----------------------------------------------------------------
// Make sure the number of chars returned matches the string length.
// -----------------------------------------------------------------
if (nChars == (ULONG)(nLen + 1))
{
regKey.Close();
return szName;
}
// ---------------------------------------------------------------------
// Wrong count of chars returned; drop through to avoid security issues.
// ---------------------------------------------------------------------
}
// --------------------------------
// Couldn't query the string value.
// --------------------------------
}
// -------------------------------
// Couldn't open the registry key.
// -------------------------------
regKey.Close();
}
// ------------------------------------------------------------------------------------
// Not a logical font, or we couldn't get the substitute, so just return the font name.
// ------------------------------------------------------------------------------------
return strLogName;
}
根据 MSDN 关于它们的文档 [^],MS Shell Dlg 和 MS Shell Dlg 2 并不是真正的字体,而是操作系统根据用户的区域设置和使用的 Windows 版本将它们与特定字体关联起来的逻辑字体类型名称。对于大多数目的,可以将逻辑名称用作字体名称,Windows 会很好地使用它们。但是,这两个名称永远不会出现在用户系统中已安装的字体列表中,因为它们不是真正的字体,因此无法安装。因此,在上面的 GetHyperlinkFontName
函数中,我们不希望返回这两个名称中的任何一个。相反,如果 GetLogFont
返回一个 LOGFONT
结构,其中 lfFaceName
设置为任一逻辑字体类型名称,我们将进入注册表查找 Windows 替换逻辑字体的实际字体名称。这就是我们返回的名称。
本文随附的演示程序(作为下载提供)演示了如何使用此功能。如果您单击对话框窗口底部附近的“默认字体”按钮(请参阅文章开头的插图),字体组合框中的名称将更改为您计算机上等同于 MS Shell Dlg 的字体。在许多情况下,这将是 Microsoft Sans Serif,但在某些区域设置和旧版本 Windows 中,它将与该名称不同。有关详细信息,请参阅上一段中提到的文档。
SizeToContent 函数
此函数将超链接的焦点矩形调整为适合超链接的实际文本。它必须被重写,因为 CMFCLinkCtrl 中的原始函数再次使用了对逻辑字体的硬编码引用,而忽略了实际用于超链接的字体。代码相当直接,所以我没有包含在这里;如果您想要详细信息,可以查看下载中的 CHyperlink.cpp
文件。我想提的主要一点是,对于图像和多行超链接,我所做的是将工作转交给 CMFCButton(用于图像超链接)或 CMFCLinkCtrl(用于多行超链接)。我没有花太多时间测试这些功能中的任何一个,因为我认为它们按照 MSDN 文档中的说明工作。但是,如果您需要使用这两种类型的超链接,您可能需要对该函数中的代码进行一些额外的修改。
支持可访问性
关于 CHyperlink 类,最后要提的是,我已经实现了 PreTranslateMessage
以提供一些额外的可访问性功能。我工作的地方有一项政策,即尽可能多地通过键盘快捷键访问代码。CMFCLinkCtrl 已经这样做了,因为如果您在超链接获得焦点时按下回车键,它将执行跳转。但是,使用空格键而不是回车键提供此功能更为常见。因此,PreTranslateMessage
将 VK_SPACE 按键转换为 VK_RETURN 值,然后将其传递给 CMFCLinkCtrl::PreTranslateMessage
。如果您的对话框窗口有一个默认按钮,这可能不会像您期望的那样工作。在这种情况下,您可以用以下代码替换 VK_RETURN 值:
SendMessage(WM_COMMAND, MAKEWPARAM(this->GetDlgCtrlID(), BN_CLICKED), (LPARAM)this->m_hWnd);
return TRUE;
在程序中使用 CHyperlink
本文的下载内容包括一个演示项目,我提供了可执行文件版本和 Visual Studio 2010 解决方案版本。该演示是一个基于对话框的 MFC 应用程序,旨在演示 CHyperlink 类的功能。本文开头处的图像显示了演示窗口的运行情况。由于演示不是本文的重点,我将在此简要概述它,并附带一些在开发演示时遇到的有趣问题的评论。
不可变和可变超链接
演示程序对话框窗口中有两个超链接,它们都指向同一个网页,并且具有相同的显示文本。第一个我称之为“不可变”,它是一个 CMFCLinkCtrl 对象。第二个称为“可变”,它是一个 CHyperlink 对象,默认情况下使用对话框的默认字体,9 磅的 Verdana。由于不可变超链接使用 afxGlobalData.fontDefaultGUIUnderline
字体,因此您可以比较这两个对象,同时更改可变超链接的字体和其他设置。如果您单击窗口底部的“默认字体”按钮,这两个超链接应该是一样的。
对话框背景
如果您想知道为什么我重新着色了对话框窗口的背景而不是使用默认背景色,那是因为使用默认背景时,组框的边框几乎看不见,除了在 Windows XP 上。我不确定 Microsoft 的设计大师为什么会创建这样一个主题,导致组框失去了它被发明的目的,但他们似乎是这样做的。由于您无法轻松地重新着色组框的边框,最简单的方法就是重新着色窗口的背景,使其边框清晰可见。
可变超链接的选项
现在您可以看到“选项”组框的边框了,这是一个显示对话框窗口的插图,其中超链接是 3D 按钮,并且一些字体设置已从默认值更改。

您可以对影响可变超链接的任何选项进行的更改列在下面:
- 选择新的字体名称。
- 更改字体的磅值。
- 选择斜体或粗体文本。
- 设置下划线仅在鼠标悬停在超链接上时出现。
- 将超链接从默认的纯文本显示更改为 CMFCButton 类支持的三种按钮样式之一。在按钮样式中,只有 XP 风格的 3D 按钮可能会得到很多使用;在我看来,另外两种相当难看。
- 将字体在焦点矩形内的对齐方式从默认的左对齐更改为居中或右对齐。如果您为超链接选择了一种按钮样式,您可能希望将其更改为居中对齐。
- 更改三个超链接颜色中的任何一个或所有颜色:普通、悬停和已访问。我将在下面讨论用于这些颜色选择器的控件。
- 单击按钮可恢复为对话框字体或不可变超链接的默认字体。这不仅会更新超链接,还会更新字体组合框和磅值组合框中的选择。
展开窗口
如果您选择较大的字体大小,您会注意到对话框窗口会扩展其宽度以显示完整的超链接。将字体重置为较小的磅值,窗口会再次收缩。您还可以通过拖动任何边框来放大或缩小窗口。但是,它只会收缩到原始显示大小,不会更小。这由 OnGetMinMaxInfo
(WM_GETMINMAXINFO 消息的处理程序)控制。如果您不知道此类处理程序的工作原理,请查看 Demo_HyperlinkDlg.cpp
文件中该处理程序的代码。
其他 VC++ 2008 Feature Pack 控件
除了 CMFCLinkCtrl 及其派生类 CHyperlink 之外,演示程序还使用了另外两个控件,它们作为 Visual C++ 2008 Feature Pack 的一部分添加到 MFC 库中:CMFCFontComboBox 和 CMFCColorButton。这两个都非常有用,值得至少看一眼。
CMFCFontComboBox 控件
字体组合框会自动用用户系统中所有已安装字体的列表填充组合框。组合框显示每种字体类型名称以及显示它是 TrueType、Raster 还是 Device 字体的图标(您可以将组合框设置为显示其中任何一种或所有类型)。这使得用户很容易在您的应用程序中更改字体。在这里,我将组合框绑定到可变超链接,以便选择不同的字体名称会立即更改超链接使用的字体。我也将其绑定到“对话框字体”和“默认字体”按钮,以便在单击任一按钮时,相应的字体名称会在组合框中被选中。
CMFCColorButton 控件
这是一个有趣的颜色选择器控件。演示程序使用三个此类控件让用户更改可变超链接的普通、悬停和已访问颜色属性。单击时,颜色按钮会打开一个 CMFCColorBar 窗口,显示 20 种颜色的选择(如图所示)。用户可以选择其中一种预选颜色,或者可以单击窗口底部的“其他”按钮(显示标签为“更多颜色...”)打开一个更全面的、双选项卡式颜色选择器。“标准”选项卡提供相当广泛的颜色选择,“自定义”选项卡允许您按数值选择或指定任何颜色,涵盖所有颜色。

我在此控件中遇到的一个奇怪之处是,如果您将 MFC 链接为静态库,Visual Studio 2010 应用程序向导并未完全支持它。向导未能将以下必需代码添加到项目的 .rc 文件中:
#ifndef _AFXDLL
#include "afxribbon.rc" // Ribbon and control bars.
#endif
结果,单击 CMFCColorBar 窗口底部的“更多颜色...”按钮无效。我通过将上述代码添加到我的 .rc2 文件中解决了这个问题。我在 Microsoft® Connect 上报告了此问题,并收到了 Pat Brenner 的回复,他说该问题已被识别并将在 Visual Studio 的下一个主要版本中修复。在此期间,如果您使用 MFC 静态库并想使用此控件,您需要将上面的代码添加到您的 .rc2 文件中。有关更多信息,您可以在 我的报告和 Brenner 的回复 [^] 中阅读。
可访问性支持
在我上次全职使用 MFC(在转向 C# 之前)时,Visual Studio 6.0 仍被广泛使用,并且在对话框窗口中支持键盘快捷键需要大量工作。在使用 Visual Studio 2010 进行此项目时,我很高兴地发现情况已经发生变化;您现在可以非常轻松地为对话框窗口中的几乎任何控件添加键盘快捷键。在演示应用程序中,我的策略是包含一个 ampersand 字符(&)在一个与每个控件(按钮除外)关联的静态标签中。静态标签必须紧接在目标控件之前(在 Tab 顺序中),但静态标签的 Tabstop 属性必须设置为 FALSE。一如既往,ampersand 紧跟在要与 Alt 键一起用作键盘快捷键的字符之前。对于按钮,ampersand 字符可以直接包含在按钮标题中。“按钮”当然包括复选框。
通过这种方式,我无需编写任何实际代码即可为对话框窗口中的几乎所有控件添加键盘快捷键。在演示项目中,您可以按 Alt 键查看快捷字符;这会将快捷字符下划线化。然后,通过按 Alt 键与快捷键一起执行特定的快捷键。对于超链接控件,这会选中超链接但不会执行它。要执行此操作,您必须按回车键或空格键(但后者仅适用于 CHyperlink 对象,即可变超链接)。
顺便说一句,由于对话框中的两个超链接控件都继承自 CMFCButton,因此技术上您可以在显示文本中添加一个 ampersand 字符来定义这些控件的键盘快捷键。但是,只有当您禁用超链接的下划线时,下划线字符才可见,因此我改用其关联的静态标签为超链接定义了快捷键。
CMFCColorBar 无键盘支持
我无法通过键盘快捷键完全支持的一个控件是单击 CMFCColorButton 控件时打开的 CMFCColorBar 窗口。颜色按钮本身工作正常;要查看此功能,请按 Alt+N 选择“普通”颜色按钮。然后按空格键打开关联的颜色条窗口。但是,如果您想在此基础上用键盘操作,那就无济于事了;没有支持使用键盘字符从该窗口中选择颜色或使用快捷键单击“更多颜色...”按钮。(不,ampersand 即使添加到“更多颜色...”按钮的标题中也不起作用。)
毫无疑问,这可以通过派生自 CMFCColorBar 的新类来实现解决方案,但这并不是本文的主要焦点,我也不想为此大费周章。相反,我将向所有有兴趣为此控件添加键盘快捷键的人发出公开邀请,否则我认为它非常有用。
历史
版本 1.0 – 2010 年 10 月 10 日
- 首次公开发布
版本 1.1 – 2011 年 1 月 11 日
- 将 CMFCHyperlink 类从 CWinAppEx 派生,而不是从 CWinApp 派生,以修复 CMFCVisualManager 的内存泄漏。请参阅下面来自 buingocdinh 的评论以及我的回复。
许可证
本文以及相关的源代码和文件,根据 Code Project Open License (CPOL) 获得许可。您可以随心所欲地使用此软件,但不得出售源代码。软件按“原样”提供,不附带任何明示或暗示的保证。我对该软件可能造成的任何损坏或业务损失概不负责。
请注意,本文提供的代码派生自 Microsoft® 拥有的和版权所有的软件,作为 Microsoft Foundation Classes for Visual C++® 的一部分。因此,它是一个衍生作品,可能受到 Microsoft® 更严格的许可政策的约束。