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

CImageButtonWithStyle - 使用 XP 可视化样式的图像按钮

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (25投票s)

2005年11月24日

CPOL

6分钟阅读

viewsIcon

278596

downloadIcon

4784

如何使使用图标或位图的按钮采用 XP 可视化样式。

引言

想象一下,您终于完成了为 MFC 应用程序添加清单文件的工作,这样您所有的控件都可以利用新的 XP 可视化样式。您的一张对话框混合了带有普通文本标题的按钮和使用图像作为标题的按钮,结果看起来像这样

Sample Dialog without CImageButtonWithStyle class

这不是您想要的!带有文本标签的按钮按照预期显示,但那些使用图像的按钮却忽略了 XP 可视化样式设置,而是以旧的 3D 效果绘制。您希望您所有的按钮都采用 XP 可视化样式,像这样

Sample Dialog using CImageButtonWithStyle class

CImageButtonWithStyle 是一个使这个过程变得简单的类,而且它不会改变您的应用程序在 XP 之前的 Windows 版本上的运行方式。

背景

Windows 应用程序在 Windows XP 下运行时,不会自动使用新的“主题感知”版本的通用控件库 comctl32.dll。您的应用程序必须将清单文件作为其资源之一,以告知 Windows 您希望使用新版本的库,因为存在一些细微的不兼容性。有关更多详细信息,请参阅 Jian Hong 的文章 为当前项目添加 XP 主题样式(或查看本文的演示应用程序)。

如果您只想测试会发生什么而不进行永久更改,只需将一个合适的清单文件复制到您的可执行文件 appname.exe 同一个目录中,并将其重命名为 appname.exe.manifest

您会注意到,使用图标或位图的按钮(使用窗口样式标志 BS_ICONBS_BITMAP)会以您在未使用可视化样式时预期的 3D 效果进行渲染。在某些情况下,例如完全填充按钮面的位图,这种行为是合理的。在其他情况下,例如混合符号和文本标题时,它只是看起来很难看。

您可能会认为有一个简单的解决方案可以选择新的外观(例如扩展样式标志),但我徒劳地搜索了一番。有许多已发布的解决方案使用了 BS_OWNERDRAW 样式来完全接管所有按钮的渲染,但这需要重新实现按钮控件的大部分基本行为(请参阅 Davide Calabro 的文章 CXPStyleButtonST v1.2 或 Ewan Ward 的文章 无需 MFC 的本机 Win32 主题感知自绘控件)。很难确定这些类在其他所有方面是否都能像普通 Windows 按钮控件一样运行。

忘记 BS_OWNERDRAW,只需使用 NM_CUSTOMDRAW 即可

幸运的是,SfaeJ 在 Ewan Ward 的文章中添加了一条评论,这让我走上了正确的道路。当使用较新版本的 comctl32.dll运行时,按钮控件会发送 NM_CUSTOMDRAW 通知。一旦我知道要查找什么,我就能从 Microsoft 的文档中找到一些零散的信息并得到一个可行的解决方案。

使用代码

使用 CImageButtonWithStyle 非常简单。

  1. 首先,将 CImageButtonWithStyle 类及其辅助类 CVisualStylesXP 的源文件添加到您的项目中(四个文件:ImageButtonWithStyle.hImageButtonWithStyle.cppVisualStylesXP.hVisualStylesXP.cpp)。
  2. 为每个将显示图像的按钮在对话框中添加一个 CButton 成员,并将其与 Windows 控件关联。如果您使用 Visual Studio 类向导,它将同时添加成员变量并在您的 CDialog 派生类的 DoDataExchange() 重写中添加对 DDX_Control( 的调用。这将把 CButton 实例与对话框模板创建的 Windows 控件关联起来。
    void CSampleDlg::DoDataExchange(CDataExchange* pDX)
    {
        CDialog::DoDataExchange(pDX);
        DDX_Control(pDX, IDC_BUTTON, m_wnd_button);
        // other DDX_ and DDV_ calls
    }
  3. 现在,在您的 CDialog 派生类的头文件中添加行 #include "ImageButtonWithStyle.h",并将 CButton 成员的声明更改为使用 CImageButtonWithSyle 类。
    CImageButtonWithStyle m_wnd_button;
  4. 重新编译即可完成。

如果您想在其他情况下使用 CImageButtonWithStyle,您将需要调用 SubclassDlgItem()SubclassWindow()CImageButtonWithStyle 实例与特定控件关联(除非您通过从 CImageButtonWithStyle 实例调用 Create() 成员函数来动态创建控件)。

演示应用程序

演示应用程序是使用 Visual Studio App-Wizard 生成的。我创建了一个最小的 SDI 应用程序,并添加了一个菜单命令“View|View Dialog...”来调用文章顶部显示的示例对话框。

幕后

我将我的新 CImageButtonWithStyle 类从 MFC CButton 类派生出来,并添加了一个 NM_CUSTOMDRAW 通知处理程序。如果使用的是旧版本的 comctl32.dll(即 XP 之前的 Windows),则不会将 NM_CUSTOMDRAW 通知发送到按钮控件,因此我的处理程序甚至不会被调用。不必担心向后兼容性,因为在这种情况下,新代码不会生效。

我没有直接调用 uxtheme.dll 的可视化样式 API 函数,而是使用了 David A. Zhao 的文章 为自绘控件添加 XP 可视化样式支持 中的 CVisualStylesXP 类来动态加载 uxtheme.dll。在运行旧版本的 Windows(其中不存在 uxtheme.dll)时,CVisualStylesXP 无法加载该库,因此它提供了一些始终失败的函数的存根版本。如果我直接调用 uxtheme.dll 函数,使用 CImageButtonWithStyle 的应用程序将无法在旧版本的 Windows 上加载。

NM_CUSTOMDRAW 处理程序 OnNotifyCustomDraw() 会收到一个指向 NMCUSTOMDRAW 结构的指针。CImageButtonWithStyle 处理程序执行以下操作:

  1. 如果按钮控件没有设置 BS_ICONBS_BITMAP 样式标志,或者 XP 可视化样式主题未在使用中,则处理程序将简单地返回 CDRF_DODEFAULT。这将导致默认窗口过程(由 comctl32.dll 提供)像往常一样绘制按钮(就好像我没有处理 NM_CUSTOMDRAW 一样)。
  2. 如果 NMCUSTOMDRAW 结构中的 dwDrawStage 成员的值为 CDDS_PREERASE,则通过调用全局 CVisualStylesXP 实例 g_xpStyleDrawThemeParentBackground() 成员来擦除背景。
  3. 通过调用 g_xpStyle.OpenThemeData() 获取 XP 可视化样式主题数据的句柄。
  4. 使用按钮的窗口样式和 NMCUSTOMDRAW 结构中的 uItemState 成员来确定用于绘制背景的按钮状态:PBS_DISABLEDPBS_PRESSEDPBS_HOTPBS_DEFAULTEDPBS_NORMAL,然后使用 g_xpStyle.DrawThemeBackground() 进行绘制。
  5. g_xpStyle.GetThemeBackgroundContentRect() 获取描述按钮图像内部的矩形。
  6. 使用 g_xpStyle.CloseThemeData() 关闭主题句柄。
  7. 使用按钮样式位 BS_LEFTBS_RIGHTBS_TOPBS_BOTTOM 来确定图像位置,然后使用 Windows 的 DrawState() 函数绘制位图或图标图像(如果按钮的窗口样式包含 WS_DISABLED 标志,则传入 DSS_DISABLED 标志)。
  8. 如果 uItemState 包含 CDIS_FOCUS 标志,则调用 DrawFocusRect() 在边框内绘制焦点矩形。
  9. 最后,将返回值设置为 CDRF_SKIPDEFAULT,以告知默认窗口过程(来自 comctl32.dll)不要绘制按钮(因为我的代码已经完成了)。
© . All rights reserved.