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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.65/5 (19投票s)

2004 年 5 月 4 日

CPOL

7分钟阅读

viewsIcon

114815

downloadIcon

3120

一个 mix-in 类,通过处理 WM_CTLCOLOR* 消息来改变对话框的外观,只需五行代码。

引言

很多时候,您希望自定义对话框中显示的颜色。也许需要一个独特、原创的外观(并且您不想走主题的路线);也许红色背景适合关键错误消息;如果您开发了一个复杂的对话框,其中一部分用作拖放目标,您可能希望强调它们;或者,在一个包含必填字段的表单中,您可能希望它们具有不同的颜色。

简单的方法是处理 WM_CTLCOLOR 系列消息:在 WTL 中处理消息的简单方法是使用一个 mix-in 类,它能完成大部分繁重的工作。

背景

winuser.h 中,您可以找到 WM_CTLCOLORMSGBOXWM_CTLCOLOREDITWM_CTLCOLORLISTBOXWM_CTLCOLORBTNWM_CTLCOLORDLGWM_CTLCOLORSCROLLBARWM_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_WINDOWCOLOR_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_SCROLLWS_VSCROLL)不会生成此消息。要自定义附加到窗口的滚动条的外观,请使用扁平滚动条函数。

并且,在扁平滚动条 API 文档中...

注意:扁平滚动条 API 在 Comctl32.dll 版本 4.71 到 5.82 中实现。Comctl32.dll 版本 6.00 及更高版本不支持扁平滚动条。

因此,除非您使用 FlatSB_SetScrollProp(),否则您的编辑框和列表框不会有重绘的滚动条,这需要调用 InitializeFlatSB() 来进行控件初始化,即使如此,如果您的操作系统运行的是 Windows XP(第一个带有Comctl32.dll 版本 6 的版本)或更高版本,调用这些函数后也不会有结果。我考虑添加几个标志,一个用于使用或不使用扁平滚动条,另一个用于查看滚动条是否已初始化,但这意味着将标志变量设为私有并提供访问器和几个设置器(至少是 OR 和 REPLACE),这看起来工作量太大,但回报微乎其微。您怎么看?如果有人知道如何自定义使用窗口样式(WS_SCROLLWS_VSCROLL)创建的滚动条的背景,我很乐意了解。

按钮

按钮不会响应您在处理 WM_CTLCOLORBTN 时使用的任何设置,除非它们是拥有者绘制的。

关于拥有者绘制的按钮,引用 MSDN

“拥有者绘制的按钮由应用程序绘制,而不是由系统绘制,并且没有预定义的样式或用法。其目的是提供一个由应用程序自行定义外观和行为的按钮。拥有者绘制的按钮的父窗口通常响应至少三个与该按钮相关的消息

  • WM_INITDIALOG
  • WM_COMMAND
  • WM_DRAWITEM

当您必须绘制拥有者绘制的按钮时,系统会向父窗口发送一个 WM_DRAWITEM 消息,其 lParam 参数是指向 DRAWITEMSTRUCT 结构的指针。该结构与所有拥有者绘制的控件一起使用,为应用程序提供绘制控件所需的信息。DRAWITEMSTRUCT 结构中的 itemActionitemState 成员定义了如何绘制拥有者绘制的按钮。”

换句话说,仅仅为了绘制一个按钮就需要做很多工作,然后主题可能会让您的生活更加艰难……我个人认为这不值得付出努力。

示例应用程序

示例应用程序是一个基于对话框的 WTL 应用程序,其中有几个复选框可以改变其外观,以及两个有趣的按钮:关于… 打开一个“关于”框,其中有一个静态变量控制其外观:有四组颜色,如下所示。每次单击按钮,您都会得到一组不同的颜色。

必填…,如下所示,更有趣。对话框将所有“必填”字段的背景绘制成浅绿色:如果其中一个字段丢失,您就不能通过单击“确定”关闭对话框,只能通过单击“取消”。

为了启用您所看到的,CRequired 类包含一个画笔成员(除了从 CCtlColored 继承的画笔)并重写了 DoHandleCtlColor(),以便它检查控件是否是“必填”编辑框之一,如果是,则绘制其背景,否则让 DoHandleCtlColor() 的默认实现处理。

参考文献

  • ATL Upside/Down Inheritance,由 Jim Beveridge 撰写,非常详细地解释了 mix-in 类的概念。如果链接失效,请在 Google 上搜索它,有很多副本。
  • Eiffel 编程语言是一种漂亮的编程语言,尽可能面向对象。尽管我自己更喜欢 C++,但我不会否认 Eiffel 有很多魅力和优雅之处。
  • “已删除的 Windows 编程元素” 列出了仅为保持向后兼容性而在 Windows 头文件中定义的符号,以及在相关情况下它们的替换项。
  • CodeProject 的“对比色”,由 alucardx 撰写,提供了其中一个附加颜色操作函数的灵感,该函数位于头文件底部。

历史

  • 2004 年 5 月 - 创建。
© . All rights reserved.