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

原生 Win32 主题感知所有者绘制控件,无需 MFC

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.30/5 (15投票s)

2002 年 11 月 29 日

5分钟阅读

viewsIcon

207220

downloadIcon

7806

如何在原生 Win32 项目中为所有者绘制控件应用主题

目录

摘要

随着 XP 的出现,微软试图确保用户界面的渲染职责现在更多地由操作系统承担,而不是程序员。在大多数情况下这很好,但仍有一些情况您希望能够自己绘制控件。最常见的情况之一是带有图像的按钮,本文将讨论使用原生 Win32 API 实现此类控件。尽管讨论专门针对所有者绘制按钮,但此处涵盖的主题将适用于所有所有者绘制控件。在本文中,我们将讨论如何实现一个简单的原生 Win32 应用程序,该应用程序包含一个主题感知的所有者绘制按钮,该按钮可在主题化和非主题化的 Microsoft Windows 32 位操作系统上运行。

讨论

对话框模板

打开示例项目(OwnerDraw.dsp)并查看 IDD_MAIN_DLG。您会看到 UI 由一个对话框和两个按钮组成,一个所有者绘制控件按钮和一个普通按钮(作为参考)。将资源脚本(OwnerDraw.rc)以文本形式打开,您会看到我们的所有者绘制按钮是一个普通的系统按钮,但设置了 BS_OWNERDRAW 按钮样式标志。

//////////////////////////////////////////////////////////////////////////
//
// Dialog
//

IDD_MAIN_DLG DIALOG DISCARDABLE  0, 0, 307, 122
STYLE DS_SETFOREGROUND | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | 
    WS_SYSMENU
CAPTION "Owner Draw Sample"
FONT 8, "MS Sans Serif"
BEGIN
    CONTROL         "Owner Draw",IDC_OWNERDRAW_BTN,"Button",BS_OWNERDRAW | 
                    WS_TABSTOP,80,20,105,35
    PUSHBUTTON      "Normal",IDC_NORMAL_BTN,80,60,105,35
    PUSHBUTTON      "OK",IDOK,250,5,50,14
    PUSHBUTTON      "Cancel",IDCANCEL,250,22,50,14
END

设置此标志后,我们就告诉 Windows,由按钮的所有者(对话框)负责绘制按钮。您将在 MainDlg.cpp 中找到对话框的实现。

什么状态!

控件在屏幕上的外观是其状态的反映。例如,按钮可能被按下或获得焦点。Windows 可以告诉我们的预定义状态是:

  • ODS_CHECKED 菜单项将被选中。此位仅用于菜单。
  • ODS_COMBOBOXEDIT 绘制发生在所有者绘制组合框的选中字段(编辑控件)中。
  • ODS_DEFAULT 该项是默认项。
  • ODS_DISABLED 该项将被绘制为禁用状态。
  • ODS_FOCUS 该项具有键盘焦点。
  • ODS_GRAYED 该项将被灰色显示。此位仅用于菜单。
  • ODS_HOTLIGHT Windows 98/Me, Windows 2000/XP:该项正在进行热跟踪,即鼠标悬停在该项上时,该项将被高亮显示。
  • ODS_INACTIVE Windows 98/Me, Windows 2000/XP:该项处于非活动状态,与菜单关联的窗口处于非活动状态。
  • ODS_NOACCEL Windows 2000/XP:控件将不显示键盘快捷键提示。
  • ODS_NOFOCUSRECT Windows 2000/XP:控件将不显示焦点指示器。
  • ODS_SELECTED 菜单项的状态为选中。

但显然并非所有这些都适用于我们的按钮。尽管 Windows 会在我们列出的任何相关状态发生变化时通知我们,但在我们的示例中,我们仅在以下情况下重绘按钮:

  • 它获得焦点
  • 它失去焦点
  • 它被按下
  • 它被释放
  • (特别是对于 XP)如果它“热”。

鼠标跟踪

XP 用户可能注意到系统按钮在鼠标悬停在按钮上时会被高亮显示(或跟踪)。但是,我们不会收到来自 Windows 的任何特殊通知,告知系统按钮正在进行热跟踪。为此,我们需要确定鼠标是否已移到按钮上方。在我们的示例中,我们使用了旧的 16 位子类化技术 - 基本上我们用我们自己的过程替换了所有者绘制按钮的窗口过程,以便我们可以监听 WM_MOUSEMOVE 消息。当鼠标移动到按钮上方时,该过程将收到 WM_MOUSEMOVE 消息,然后我们可以高亮显示重绘按钮。

接收消息

操作系统发送的 WM_DRAWITEM 通知将促使我们重绘按钮。请注意,通知中包含了有关 Windows 当前按钮状态的信息。结合从子类化中获取的额外状态信息,我们现在拥有足够的信息来渲染按钮。

双击

当所有者绘制按钮被双击时,它会发送 BN_DBLCLK 通知,而不是像被按下和释放两次一样做出响应。为了改变这种行为,当我们的所有者绘制按钮收到 WM_LBUTTONDBLCLK 消息时,我们可以让它将 WM_LBUTTONDOWN 通知发布回给自己。

主题

到目前为止,一切顺利。下一个问题是决定按钮应该是什么样子。第一步是确定操作系统是否支持主题。InitThemes() 函数尝试动态加载 UXTHEME.DLL。不要尝试静态链接到 UXTHEME 库,因为应用程序将在不附带该 dll 的系统上失败。我们加载以下函数:OpenThemeData, GetThemeBackgroundContentRect, DrawThemeBackground, DrawThemeText,CloseThemeData

绘制按钮

然后,绘制按钮是一个四步过程:

  • 第一步 - 绘制背景(包括边框)。
  • 第二步 - 绘制图标。
  • 第三步 - 写入文本。
  • 第四步 - 绘制焦点矩形。

主题变更

当用户更改主题时,操作系统会发送 WM_THEMECHANGED 消息。然后,只需卸载并重新加载主题库即可确保按钮正确渲染。

重用示例代码

要为您的应用程序添加基本的主题支持,请从 MainDlg.cpp 中复制第 27 到 82 行(包含)。您必须包含 UXTHEME.HTMSCHEMA.H 头文件。请记住,在应用程序首次启动时还要包含一个清单并调用 InitCommonControlsPrepareImageRectDrawTheIcon 函数用于将图标放在按钮上。WM_DRAWITEM 处理程序演示了如何根据操作系统绘制主题化和非主题化按钮。

致谢

PrepareImageRectDrawTheIcon() 函数取自 Davide Calabro 的 **CButtonST** 类。

© . All rights reserved.