Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WTL

Changing the Colors of a WTL Dialog (The Easy Way)

Rate me:
Please Sign up or sign in to vote.
4.65/5 (19 votes)
10 Sep 2013CPOL7 min read 110.9K   3.1K   46   16
A mix-in class to change the appearance of a dialog, by handling WM_CTLCOLOR* messages, with five lines of code.

Image 1

Introduction

Quite often, you want to customize the colors shown in your dialogs. Maybe a unique, original look is required (and you don't want to go all the way into skinning); perhaps a red background seems suitable for critical error messages; if you've developed a complex dialog, parts of which serve as drop targets, you might want to emphasize those: or, in a form with required fields, you might want a different color for them.

The easy way to do this, is handle the WM_CTLCOLOR family of messages: the easy way to handle messages in WTL, is to use a mixin class which does most of the grunt work.

Background

In winuser.h, you can find the definitions of WM_CTLCOLORMSGBOX, WM_CTLCOLOREDIT, WM_CTLCOLORLISTBOX, WM_CTLCOLORBTN, WM_CTLCOLORDLG, WM_CTLCOLORSCROLLBAR, and WM_CTLCOLORSTATIC.

By checking MSDN, you find out that all their handlers have a lot in common:

  • They all receive a handle to the device context (HDC) for the relevant control in their wParam.
  • They all receive a handle to the relevant control (HWND) in their lParam.
  • They all return a handle to a brush (HBRUSH), which will be used to erase the control's background (unless, of course, you handle WM_ERASEBKGND yourself).

What's left to do? Implement a mixin with a message map which calls the same handler for all of them, with one overrideable function and a few data members for customization, and you're done! Well, that mixin is already written. I hope you'll find it as useful as I found so many CodeProject samples.

By the way, "Deleted Windows Programming Elements" in MSDN mentions WM_CTLCOLORMSGBOX as gone, never again to return. It was part of 16-bit Windows.

Using the code

Five lines of code, that's all it takes:

  • Include the relevant header (CCtlColor.h).
  • Add the mixin (CCtlColored<>) to your inheritance list, with whatever flags you consider relevant.
  • Chain to the mixin's message map.
  • Optionally, initialize the text and background colors, if COLOR_WINDOW and COLOR_WINDOWTEXT are not suitable for your purpose.

Here is a sample, which repaints a wizard-generated 'about box'.

C++
#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;
    }
};

What else can be done (if you really, really want to)

The exposed functions

You can change the dialog's appearance at run time, using the exposed functions:

// 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);

Little can be added. The first two are the ones I personally used most often, the third is an easy shortcut when you want to use system colors; the last one is the heaviest tool, both powerful and hard to use.

Flags

A set of flags, defined in an enum at the beginning of the header, enables managing the messages the class will handle. For each of the WM_CTLCOLOR* messages there's a FLG_HANDLE_* flag, that, if it's set (either at creation time or at run time), will enable managing the corresponding message. The flags are passed as a parameter to the template (as usual, with a "reasonable default"), and can be modified through the protected member m_Flags.

As mentioned above, "Deleted Windows Programming Elements" in MSDN mentions WM_CTLCOLORMSGBOX is obsolete (part of 16-bit Windows only), so it is commented out in the source, and so is the corresponding flag. Who knows, some day they might be back.

The overrideable function

In the best tradition of WTL, one of the functions in the class is overrideable:

C++
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.

The default handler sets the text color and background of the passed HDC using the relevant member variables, and then returns the member brush handle, which will be used by Windows' DefWindowProc() when handling WM_ERASEBKGND.

In the sample app, there are a couple of overrides: one in

class 
CMainDlg
, which shows read-only edit boxes in a color scheme different from that used for writeable edit boxes (and everything else), the other in class CRequiredDlg, which paints the background of "required fields" in a different color (light green).

Basically, you can do whatever you like in an override (if you decide to use one), but always return a valid brush.

The protected (accessible) members

All data members are protected, which means they are accessible by your class. Still, only one is meant to be directly modified: m_Flags. All others are accessible just to enable using them if you override DoHandleCtlColor(), putting accessors for them seemed a bit of an overkill. Object-oriented purists might complain, but then, they all write in Eiffel, don't they?

Changing the looks of common dialogs (such as File Open)

Sorry, it's not that easy! You have to create a common dialog, and subclass it before its shown. That, at least, is the theory: I must confess that I didn't, yet. I hope I'll be able to add that feature in the near future.

Points of Interest

Track Bars

A TrackBar control does not repaint itself until it gets the focus. So, if you enable run time changes of appearance, and you have one, you'll have to set and reset the focus in order to show it properly.

Scroll Bars

Scroll bars respond to the WM_CTLCOLORSCROLLBAR message only if they are direct children of your dialog: according to MSDN,

The WM_CTLCOLORSCROLLBAR message is used only by child scrollbar controls. Scrollbars attached to a window (WS_SCROLL and WS_VSCROLL) do not generate this message. To customize the appearance of scrollbars attached to a window, use the flat scroll bar functions.

And, in the flat scroll bar API documentation...

Note: Flat scroll bar APIs are implemented in Comctl32.dll versions 4.71 through 5.82. Comctl32.dll versions 6.00 and higher do not support flat scroll bars.

So, your edit and list boxes will not have repainted scrollbars unless you use FlatSB_SetScrollProp(), which requires calling InitializeFlatSB() on control initialization, and even then, there will be no result after calling these functions, if your OS is running Windows XP (first one with Comctl32.dll version 6), or later. I considered adding a couple of flags, one to use or not flat scroll bars, other to see if scroll bars have been initialized, but that meant making the flag variable private and providing an accesor and a couple of setters (at least OR and REPLACE), it looked like too much work for too ephemeras a reward. What's your opinion? If anyone there knows of a way to customize the background of a scrollbar created using a window style (WS_SCROLL, WS_VSCROLL), I'll be glad to learn.

Buttons

Buttons do not respond to whatever settings you used while handling WM_CTLCOLORBTN, unless they're owner-drawn.

On owner-drawn buttons, quote MSDN:

"An owner-drawn button is painted by the application, not by the system, and has no predefined appearance or usage. Its purpose is to provide a button whose appearance and behavior are defined by the application alone. The parent window of an owner-drawn button typically responds to at least three messages for the button:

  • WM_INITDIALOG
  • WM_COMMAND
  • WM_DRAWITEM

When you must paint an owner-drawn button, the system sends the parent window a WM_DRAWITEM message whose lParam parameter is a pointer to a DRAWITEMSTRUCT structure. Use this structure with all owner-drawn controls to provide the application with the information it requires to paint the control. The itemAction and itemState members of the DRAWITEMSTRUCT structure define how to paint an owner-drawn button."

In other words, quite a lot of work just to paint a button, and then themes might make your life even harder... Personally, I don't consider it's worth the effort.

The Sample App

The sample application is a dialog-based WTL application, which has a few checkboxes to alter its appearance, and two interesting buttons: About... opens an 'About box', which has a static variable that controls its appearance: there are four color sets, shown below. Each time you click the button, you'll get a different one.

Image 2

Required..., shown below, is more interesting. The dialog paints the background of all 'required' fields in light green: if one of those is missing, you cannot close the dialog by clicking OK, only by clicking CANCEL.

Image 3

In order to enable what you see, the class CRequired holds a brush member (in addition to the one inherited from CCtlColored), and overrides DoHandleCtlColor() so that it checks if the control is one of the 'required' edit boxes, and if so, it paints its background, otherwise letting the default implementation of DoHandleCtlColor() handle things.

References

  • ATL Upside/Down Inheritance, by Jim Beveridge, explains very nicely the concept of mix-in classes. If the link ever breaks, Google for it, there are quite a few copies around.
  • The Eiffel programming language is a beautiful programming language, as object-oriented as you can get. Even though I myself prefer C++, I won't deny there's a lot of charm and elegance in Eiffel.
  • "Deleted Windows Programming Elements" holds a list of symbols defined in Windows headers just to keep backwards compatibility, and their replacements, wherever relevant.
  • Contrasting Colors, in CodeProject, by alucardx, provides the idea for one of the bonus color-manipulation functions found at the bottom of the header.

History

  • 2004, May - Created.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Israel Israel
Pablo writes code for a living, in C++, C#, and SQL.

To make all that work easier, he uses some C++ libraries: STL, ATL & WTL (to write Windows applications), and code generation.

Pablo was born in 1963, got married in 1998, and is the proud father of two wonderful girls.

Favorite quotes:
"Accident: An inevitable occurrence due to the action of immutable natural laws." (Ambrose Bierce, "The Devil's Dictionary", published in several newspapers between 1881 and 1906).
"You are to act in the light of experience as guided by intelligence" (Rex Stout, "In the Best Families", 1950).

Comments and Discussions

 
Questionerror linking files Pin
Greg Thompson13-Jun-08 2:34
Greg Thompson13-Jun-08 2:34 
AnswerRe: error linking files Pin
Pablo Aliskevicius14-Jun-08 20:00
Pablo Aliskevicius14-Jun-08 20:00 
QuestionWindow header Pin
Chernyvski Pavel2-Jun-06 4:45
Chernyvski Pavel2-Jun-06 4:45 
AnswerRe: Window header Pin
Pablo Aliskevicius3-Jun-06 18:44
Pablo Aliskevicius3-Jun-06 18:44 
GeneralChanging Radio Button Color Pin
Member 21327262-Aug-05 3:35
Member 21327262-Aug-05 3:35 
GeneralRe: Changing Radio Button Color Pin
Pablo Aliskevicius2-Aug-05 20:16
Pablo Aliskevicius2-Aug-05 20:16 
GeneralRe: Changing Radio Button Color Pin
Member 21327263-Aug-05 4:49
Member 21327263-Aug-05 4:49 
GeneralBuilding errors Pin
RobertOls19746-May-05 4:22
RobertOls19746-May-05 4:22 
GeneralRe: Building errors Pin
Pablo Aliskevicius7-May-05 18:38
Pablo Aliskevicius7-May-05 18:38 
GeneralRe: Building errors Pin
RobertOls19749-May-05 2:47
RobertOls19749-May-05 2:47 
GeneralRe: Building errors Pin
Pablo Aliskevicius9-May-05 4:07
Pablo Aliskevicius9-May-05 4:07 
QuestionHave you tried this with a TabCtrl? Pin
noname795-May-04 6:01
noname795-May-04 6:01 
AnswerRe: Have you tried this with a TabCtrl? Pin
Pablo Aliskevicius8-May-04 18:28
Pablo Aliskevicius8-May-04 18:28 
GeneralWell done! Pin
Rob Caldecott3-May-04 22:33
Rob Caldecott3-May-04 22:33 
GeneralRe: Well done! Pin
Pablo Aliskevicius4-May-04 3:36
Pablo Aliskevicius4-May-04 3:36 
GeneralRe: Well done! Pin
joyjjjz23-Sep-08 22:17
joyjjjz23-Sep-08 22:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.