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






4.30/5 (15投票s)
2002 年 11 月 29 日
5分钟阅读

207220

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.H 和 TMSCHEMA.H 头文件。请记住,在应用程序首次启动时还要包含一个清单并调用 InitCommonControls
。PrepareImageRect
和 DrawTheIcon
函数用于将图标放在按钮上。WM_DRAWITEM
处理程序演示了如何根据操作系统绘制主题化和非主题化按钮。
致谢
PrepareImageRect
和 DrawTheIcon()
函数取自 Davide Calabro 的 **CButtonST** 类。