一些 Mix-in 类,让您的 ATL/WTL 程序拥有更好的外观
本文讨论了一些用于 WTL 的 MixIn 类。这些 MixIn 类提供了 WTL/ATL 框架不支持的某些功能,例如所有者绘制菜单、换肤的对话框按钮和静态文本控件。
目录
引言
我用 MFC 已经很长时间了,它是一个非常好的框架。但 MFC 也有一些缺点,尤其是在计划和开发小型内存驻留程序方面。对于这类程序,我们希望程序体积小,占用资源少,并且没有资源泄漏。显然,这并不是 MFC 的优势,所以我开始研究 WTL。与 MFC 不同,WTL 没有内置对所有者绘制菜单的支持。我编写了 CMenuHelp
类,用于普通的 WTL 窗口来换肤窗口的弹出菜单。CWzButtonImpl
是一个简单的所有者绘制按钮类,它与 CButtonHelp
配合使用,CButtonHelp
使用 CWzButtonImpl
来子类化对话框中的所有按钮。CButtonHelp
也可以与其他所有者绘制按钮类配合使用。CCtrlColor
处理对话框窗口的 WM_CTLCOLORDLG
和 WM_CTLCOLORSTATIC
消息,使用模板参数来更改对话框的背景颜色和控件的文本颜色。
由于我不是 WTL/ATL 框架方面的专家,我不能保证代码能适用于所有人。任何建议都受欢迎。您可能会注意到,这篇文章有时存在许多语法错误甚至完全错误的单词,这是因为我不是英语母语者。我希望大家都能理解这篇文章。
CMenuHelp
我们知道,当菜单即将弹出时,Windows 系统会向菜单的所有者窗口发送 WM_INITMENU
和 WM_ENTERMENULOOP
消息;当菜单消失时,Windows 系统会向所有者窗口发送 WM_EXITMENULOOP
消息。如果菜单项具有 MFT_OWNERDRAW
属性,Windows 还会发送 WM_MEASUREITEM
来测量菜单项,并发送 WM_DRAWITEM
消息让所有者窗口绘制菜单项。
CMenuHelp
是一个模板类,它也是一个 MixIn 类。它处理所有者窗口的这 5 条消息;在 WM_INITMENU
消息处理中,它获取活动菜单的句柄(菜单尚未显示),并调用 AttachActiveMenu()
为该菜单中的所有菜单项添加 MFT_OWNERDRAW
属性。当菜单消失时,将调用 DetachActiveMenu()
,此方法会移除 AttachActiveMenu()
添加的 MFT_OWNERDRAW
属性。
CMenuHelp 的属性
HMENU CMenuHelp::m_hActiveMenu
存储即将弹出的菜单的句柄,OnMeasureItem
和 OnDrawItem
使用此句柄对弹出菜单执行一些操作。
COLORREF CMenuHelp::m_crText
用于绘制普通菜单项文本的颜色。
COLORREF CMenuHelp::m_crHiText
用于绘制选中菜单项文本的颜色。
COLORREF CMenuHelp::m_crBkGnd
用于绘制菜单项背景的颜色。
COLORREF CMenuHelp::m_crHiBkGnd
用于绘制选中菜单项背景的颜色。
CMenuHelp 的方法
HMENU CMenuHelp::AttachActiveMenu(HMENU hActiveMenu)
将即将弹出的菜单句柄附加到 m_hActiveMenu
。hActiveMenu
是新菜单的句柄。AttachActiveMenu()
将为该菜单中的所有菜单项添加 MFT_OWNERDRAW
属性,因此系统会向我们的窗口发送 WM_MEASUREITEM
和 WM_DRAWITEM
消息。CMenuHelp
处理这两个消息来绘制菜单。AttachActiveMenu()
返回最后一个弹出菜单的句柄。
HMENU CMenuHelp::DetachActiveMenu()
从 m_hActiveMenu
的所有菜单项中移除 MFT_OWNERDRAW
属性,并将 m_hActiveMenu
设置为 NULL
。它返回当前弹出菜单的句柄。
在您的代码中使用 CMenuHelp
在您的代码中使用此类非常简单。将 "MenuHelp.h" 包含到您的项目中,将CMenuHelp
添加到您窗口的派生链中,如下所示:class CMainDlg : public CDialogImpl<CMainDlg>, public CMenuHelp<CMainDlg>最后,将声明信息添加到消息链中:
BEGIN_MSG_MAP(CMainDlg) ... CHAIN_MSG_MAP(CMenuHelp<CMainDlg>) ... END_MSG_MAP()这样就可以了,该窗口的所有弹出菜单将由我们的
CMenuHelp
绘制,而不是 Windows 的普通菜单外观。CButtonHelp
CButtonHelp
是一个帮助类,用于更改对话框中按钮控件的外观。在对话框初始化时,它修改并为所有按钮添加 BS_OWNERDRAW
属性,并将这些按钮控件链接到 CWzButtonImpl
类。CWzButtonImpl 是真正的所有者绘制按钮类。CWzButtonImpl
使用 SubclassWindow
来接管标准 Windows 按钮的测量和绘制行为,就像其他大多数 MFC 的 CButton
派生类一样。
CWzButtonImpl
CWzButtonImpl
是一个派生自 CButton
(ATL 类)的所有者绘制按钮类。它的使用很简单,就像大多数 MFC 的所有者绘制按钮类一样。首先声明一个 CWzButtonImpl
对象,然后调用 SubclassWindow
方法将其附加到按钮控件。如果您不喜欢当前的样式,可以在此类中修改绘制代码来更改按钮控件的外观。您也可以用其他所有者绘制按钮类来替换此类,您可以在 CodeProject 上找到很多。您也可以在您的应用程序中指定多个不同的所有者绘制按钮类(样式),但一个对话框中只能有一个类。
CButtonHelp 的属性
CSimpleArray<t_ButtonClass *> CButtonHelp::m_arButtons
存储对话框中的所有 t_ButtonClass
对象,每个 t_ButtonClass
对象都附加到一个按钮。t_ButtonClass
是一个模板参数,在我们的示例代码中应该是 CWzButtonImpl
。
CButtonHelp 的方法
BOOL CButtonHelp::SubclassAllButtons()
将对话框中的按钮附加到 t_ButtonClass
类对象。通常会在窗口的初始化函数中调用,例如 CDialog::OnInitDialog
。
BOOL CButtonHelp::SubclassButton(HWND hBtnWnd)
创建一个 t_ButtonClass
对象并将其附加到普通的按钮控件。hBtnWnd
是按钮控件的 HWND
。
BOOL CButtonHelp::UnSubclassButton(HWND hBtnWnd)
从按钮控件中分离 t_ButtonClass
对象。hBtnWnd
是按钮控件的 HWND
。t_ButtonClass
对象将被从 CMenuHelp::m_arButtons
中删除。
int CButtonHelp::FindButtons(HWND hBtnWnd)
用于确定一个按钮控件是否已经被子类化。
在您的代码中使用 CButtonHelp
首先将 ButtonHelp.h 包含到您的项目中,然后将CButtonHelp
类添加到对话框的派生链中,如下所示:class CMainDlg : public CDialogImpl<CMainDlg>, public CButtonHelp<CMainDlg,CWzButtonImpl>最后,不要忘记在
CDialog::OnInitDialog
中调用 SubclassAllButtons()
。CCtrlColor
CCtrlColor
类处理一些 WM_CTLCOLOR
* 消息(目前仅支持 WM_CTLCOLORDLG
和 WM_CTLCOLORSTATIC
),使用特殊的文本颜色和背景颜色(由模板参数指定)绘制对话框中的控件。这是一个非常简单的类,只有 34 行 C++ 代码。
CCtrlColor 的属性
HBRUSH CCtrlColor::m_brBkgnd
HBRUSH GDI 对象
CCtrlColor
类使用此画笔来绘制对话框和控件的背景。CCtrlColor
在构造函数中创建此对象;使用模板参数 t_crBkgnd
指定的颜色,并在析构函数中删除此对象。
在您的代码中使用 CCtrlColor
CCtrlColor
有三个模板参数:T
、t_crTextColor
和 t_crBkgnd
。后两个是可选的,默认值为 RGB(64,64,255) 和 RGB(222,222,222)。因此,您可以通过两种方法将 CCtrlColor
添加到对话框的派生链中:
class CMainDlg : public CDialogImpl<CMainDlg>, public CCtrlColor<CMainDlg>或
class CAboutDlg : public CDialogImpl<CAboutDlg>, public CCtrlColor<CAboutDlg,RGB(64,128,64)>与
CMenuHelp
相同,将声明信息添加到消息链中:BEGIN_MSG_MAP(CMainDlg) ... CHAIN_MSG_MAP(CCtrlColor<CMainDlg>) ... END_MSG_MAP()
关于演示代码
所有源代码都组织在一个 VC6 项目中,没有在 VC7 编译器上进行测试。由于本文讨论的是 ATL/WTL,您还应该安装 WTL 来编译源代码。此代码的使用没有任何限制,可以用于任何目的。