更改 WTL 对话框的颜色(简单方法)






4.65/5 (19投票s)
一个 mix-in 类,通过处理 WM_CTLCOLOR* 消息来改变对话框的外观,只需五行代码。
引言
很多时候,您希望自定义对话框中显示的颜色。也许需要一个独特、原创的外观(并且您不想走主题的路线);也许红色背景适合关键错误消息;如果您开发了一个复杂的对话框,其中一部分用作拖放目标,您可能希望强调它们;或者,在一个包含必填字段的表单中,您可能希望它们具有不同的颜色。
简单的方法是处理 WM_CTLCOLOR
系列消息:在 WTL 中处理消息的简单方法是使用一个 mix-in 类,它能完成大部分繁重的工作。
背景
在 winuser.h 中,您可以找到 WM_CTLCOLORMSGBOX
、WM_CTLCOLOREDIT
、WM_CTLCOLORLISTBOX
、WM_CTLCOLORBTN
、WM_CTLCOLORDLG
、WM_CTLCOLORSCROLLBAR
和 WM_CTLCOLORSTATIC
的定义。
通过查阅 MSDN,您会发现所有这些处理程序都有很多共同点
- 它们都在
wParam
中接收与相关控件相关的设备上下文句柄 (HDC
)。 - 它们都在
lParam
中接收相关的控件句柄 (HWND
)。 - 它们都返回一个画笔句柄 (
HBRUSH
),该画笔将用于擦除控件的背景(除非您自己处理WM_ERASEBKGND
)。
还剩下什么要做?实现一个带有消息映射的 mix-in,该映射调用同一个处理程序来处理所有这些消息,并带有一个可重写的函数和几个用于自定义的数据成员,然后您就完成了!嗯,这个 mix-in 已经写好了。希望您觉得它和我在 CodeProject 上找到的许多示例一样有用。
顺便说一句,MSDN 中的 “已删除的 Windows 编程元素” 提到了 WM_CTLCOLORMSGBOX
已被移除,不再返回。它是 16 位 Windows 的一部分。
使用代码
五行代码,仅此而已
- 包含相关的头文件(CCtlColor.h)。
- 将 mix-in (
CCtlColored<>
) 添加到您的继承列表中,并使用您认为相关的任何标志。 - 链接到 mix-in 的消息映射。
- 如果
COLOR_WINDOW
和COLOR_WINDOWTEXT
不适合您的目的,可以选择性地初始化文本和背景颜色。
这里有一个示例,它重新绘制了向导生成的“关于”框。
#include <CCtlColor.h> // (One)
class CAboutDlg : public CDialogImpl<CAboutDlg>
, public CCtlColored<CAboutDlg> // Add this line (Two)
{
public:
enum { IDD = IDD_ABOUTBOX };
BEGIN_MSG_MAP(CAboutDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_ID_HANDLER(IDOK, OnCloseCmd)
COMMAND_ID_HANDLER(IDCANCEL, OnCloseCmd)
// Add this line. CColoredThis is typedefed
// inside CCtlColored<> for your comfort.
CHAIN_MSG_MAP(CColoredThis) // (Three)
END_MSG_MAP()
LRESULT CAboutDlg::OnInitDialog(UINT uMsg,
WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
// Add next two lines...
SetTextBackGround(0xFfbF9F); // Lightish kind of blue (Four)
SetTextColor(RGB(0X60, 0, 0)); // Dark red
// (Five lines, as promised!)
// ...or, if that's your pleasure, the next two...
SetTextColor(::GetSysColor(COLOR_INFOTEXT)); (Four)
SetBkBrush(COLOR_INFOBK); // (Five lines, as promised!)
// ...or, if you're satisfied with the default
// COLOR_WINDOW/COLOR_WINDOWTEXT, do nothing!
CenterWindow(GetParent());
return TRUE;
}
LRESULT CAboutDlg::OnCloseCmd(WORD wNotifyCode,
WORD wID, HWND hWndCtl, BOOL& bHandled)
{
EndDialog(wID);
return 0;
}
};
还可以做什么(如果您真的、真的想的话)
暴露的函数
您可以使用暴露的函数在运行时更改对话框的外观
// Function name : SetTextColor // Description : Replaces the current text color. // Return type : COLORREF (the former text color) // Argument : COLORREF newColor - The new text color. COLORREF SetTextColor(COLORREF newColor); // Function name : SetTextBackGround // Description : Sets the passed color as text background, // and creates a solid // brush from it, to erase the background // of controls before // drawing on them. // Return type : COLORREF (The former text background) // Argument : COLORREF newColor - The new text background. COLORREF SetTextBackGround(COLORREF newColor); // Function name : SetBkBrush // Description : This function sets the background color and brush, // using ::GetSysColorBrush(nIndex) // and ::GetSysColor(nIndex). // It returns the former brush (in case // you want to delete it). // Return type : HBRUSH - The former brush // Argument : int nIndex - One of the ::GetSysColor() indexes. HBRUSH SetBkBrush(int nIndex); // Function name : SetBkBrush // Description : This function gives the caller maximum latitude, // letting // it set any brush (not necessarily solid) // and any background // color (not necessarily similar to the brush's color). // Return type : HBRUSH - The former brush // Argument : HBRUSH NewBrush - The new brush you'd like to set. // Argument : bool bManaged - // If true, the class will adopt the brush // and delete it as needed. // Argument : COLORREF clrBackGround - Since any brush // goes, the caller // should send a background color as similar // as possible to that of the brush. HBRUSH SetBkBrush(HBRUSH NewBrush, bool bManaged = false, COLORREF clrBackGround = CLR_INVALID);
几乎没什么可以添加的。前两个是我个人最常使用的,第三个是一个简单的快捷方式,当您想使用系统颜色时;最后一个是最强大的工具,既强大又难以使用。
标志
在头文件开头定义的 enum
中的一组标志,允许管理类将要处理的消息。对于每个 WM_CTLCOLOR*
消息,都有一个 FLG_HANDLE_*
标志,如果设置了该标志(在创建时或运行时),将启用对相应消息的管理。这些标志作为模板的参数传递(一如既往,带有“合理的默认值”),并且可以通过受保护的成员 m_Flags
进行修改。
如上所述,MSDN 中的 “已删除的 Windows 编程元素” 提到了 WM_CTLCOLORMSGBOX
已过时(仅是 16 位 Windows 的一部分),因此它在源代码中被注释掉了,相应的标志也是。谁知道呢,也许有一天它们会回来。
可重写的函数
遵循 WTL 的最佳传统,类中的一个函数是可重写的
LRESULT DoHandleCtlColor(
UINT uMsg, // One of the WM_CTLCOLOR* messages.
HDC hdc, // DC of the control which sent the message.
HWND hw) // Which control sent the message.
默认处理程序使用相关的成员变量设置传递的 HDC
的文本颜色和背景,然后返回成员画笔句柄,Windows 的 DefWindowProc()
在处理 WM_ERASEBKGND
时将使用该句柄。
在示例应用程序中,有几个重写:一个在 class CMainDlg
中,它以与可写编辑框(以及其他所有内容)不同的颜色方案显示只读编辑框;另一个在 class CRequiredDlg
中,它将“必填字段”的背景绘制成不同的颜色(浅绿色)。
基本上,如果您决定使用重写,您可以在其中做任何您想做的事情,但始终返回一个有效的画笔。
受保护的(可访问的)成员
所有数据成员都是 protected
,这意味着它们可以被您的类访问。尽管如此,只有一个是旨在直接修改的:m_Flags
。其他所有成员都只是为了在您重写 DoHandleCtlColor()
时能够使用它们,为它们提供访问器似乎有点小题大做。面向对象的纯粹主义者可能会抱怨,但他们都写 Eiffel,不是吗?
更改通用对话框(如“文件打开”)的外观
抱歉,没那么简单!您必须创建一个通用对话框,并在显示它之前对其进行子类化。至少,这就是理论:我必须承认,我还没有这样做。我希望我能在不久的将来添加这个功能。
关注点
滑块
滑块控件在获得焦点之前不会重绘自身。因此,如果您启用了运行时的外观更改,并且有一个滑块,您将不得不设置和重置焦点才能正确显示它。
滚动条
滚动条仅在它们是对话框的直接子控件时响应 WM_CTLCOLORSCROLLBAR
消息:根据 MSDN,
WM_CTLCOLORSCROLLBAR
消息仅供子滚动条控件使用。附加到窗口的滚动条(WS_SCROLL
和WS_VSCROLL
)不会生成此消息。要自定义附加到窗口的滚动条的外观,请使用扁平滚动条函数。
并且,在扁平滚动条 API 文档中...
注意:扁平滚动条 API 在 Comctl32.dll 版本 4.71 到 5.82 中实现。Comctl32.dll 版本 6.00 及更高版本不支持扁平滚动条。
因此,除非您使用 FlatSB_SetScrollProp()
,否则您的编辑框和列表框不会有重绘的滚动条,这需要调用 InitializeFlatSB()
来进行控件初始化,即使如此,如果您的操作系统运行的是 Windows XP(第一个带有Comctl32.dll 版本 6 的版本)或更高版本,调用这些函数后也不会有结果。我考虑添加几个标志,一个用于使用或不使用扁平滚动条,另一个用于查看滚动条是否已初始化,但这意味着将标志变量设为私有并提供访问器和几个设置器(至少是 OR 和 REPLACE),这看起来工作量太大,但回报微乎其微。您怎么看?如果有人知道如何自定义使用窗口样式(WS_SCROLL
、WS_VSCROLL
)创建的滚动条的背景,我很乐意了解。
按钮
按钮不会响应您在处理 WM_CTLCOLORBTN
时使用的任何设置,除非它们是拥有者绘制的。
关于拥有者绘制的按钮,引用 MSDN
“拥有者绘制的按钮由应用程序绘制,而不是由系统绘制,并且没有预定义的样式或用法。其目的是提供一个由应用程序自行定义外观和行为的按钮。拥有者绘制的按钮的父窗口通常响应至少三个与该按钮相关的消息
WM_INITDIALOG
WM_COMMAND
WM_DRAWITEM
当您必须绘制拥有者绘制的按钮时,系统会向父窗口发送一个
WM_DRAWITEM
消息,其lParam
参数是指向DRAWITEMSTRUCT
结构的指针。该结构与所有拥有者绘制的控件一起使用,为应用程序提供绘制控件所需的信息。DRAWITEMSTRUCT
结构中的itemAction
和itemState
成员定义了如何绘制拥有者绘制的按钮。”
换句话说,仅仅为了绘制一个按钮就需要做很多工作,然后主题可能会让您的生活更加艰难……我个人认为这不值得付出努力。
示例应用程序
示例应用程序是一个基于对话框的 WTL 应用程序,其中有几个复选框可以改变其外观,以及两个有趣的按钮:关于… 打开一个“关于”框,其中有一个静态变量控制其外观:有四组颜色,如下所示。每次单击按钮,您都会得到一组不同的颜色。
必填…,如下所示,更有趣。对话框将所有“必填”字段的背景绘制成浅绿色:如果其中一个字段丢失,您就不能通过单击“确定”关闭对话框,只能通过单击“取消”。
为了启用您所看到的,CRequired
类包含一个画笔成员(除了从 CCtlColored
继承的画笔)并重写了 DoHandleCtlColor()
,以便它检查控件是否是“必填”编辑框之一,如果是,则绘制其背景,否则让 DoHandleCtlColor()
的默认实现处理。
参考文献
- ATL Upside/Down Inheritance,由 Jim Beveridge 撰写,非常详细地解释了 mix-in 类的概念。如果链接失效,请在 Google 上搜索它,有很多副本。
- Eiffel 编程语言是一种漂亮的编程语言,尽可能面向对象。尽管我自己更喜欢 C++,但我不会否认 Eiffel 有很多魅力和优雅之处。
- “已删除的 Windows 编程元素” 列出了仅为保持向后兼容性而在 Windows 头文件中定义的符号,以及在相关情况下它们的替换项。
- CodeProject 的“对比色”,由 alucardx 撰写,提供了其中一个附加颜色操作函数的灵感,该函数位于头文件底部。
历史
- 2004 年 5 月 - 创建。