Skip to main content
Email Password   helpLost your password?

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:

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:

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

#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:

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,

And, in the flat scroll bar API documentation...
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 ephimerous 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:

(end of MSDN quote)

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.

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.

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

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
Questionerror linking files Pin
Greg Thompson
3:34 13 Jun '08  
AnswerRe: error linking files Pin
Pablo Aliskevicius
21:00 14 Jun '08  
QuestionWindow header Pin
Chernyvski Pavel
5:45 2 Jun '06  
AnswerRe: Window header Pin
Pablo Aliskevicius
19:44 3 Jun '06  
GeneralChanging Radio Button Color Pin
prabhu006@yahoo.co.in
4:35 2 Aug '05  
GeneralRe: Changing Radio Button Color Pin
Pablo Aliskevicius
21:16 2 Aug '05  
GeneralRe: Changing Radio Button Color Pin
prabhu006@yahoo.co.in
5:49 3 Aug '05  
GeneralBuilding errors Pin
RobertOls1974
5:22 6 May '05  
GeneralRe: Building errors Pin
Pablo Aliskevicius
19:38 7 May '05  
GeneralRe: Building errors Pin
RobertOls1974
3:47 9 May '05  
GeneralRe: Building errors Pin
Pablo Aliskevicius
5:07 9 May '05  
GeneralHave you tried this with a TabCtrl? Pin
noname79
7:01 5 May '04  
GeneralRe: Have you tried this with a TabCtrl? Pin
Pablo Aliskevicius
19:28 8 May '04  
GeneralWell done! Pin
Robert Edward Caldecott
23:33 3 May '04  
GeneralRe: Well done! Pin
Pablo Aliskevicius
4:36 4 May '04  
GeneralRe: Well done! Pin
joyjjjz
23:17 23 Sep '08  


Last Updated 3 May 2004 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2009