![]() |
Platforms, Frameworks & Libraries »
WTL »
General
Intermediate
Changing the Colors of a WTL Dialog (The Easy Way)By Pablo AliskeviciusA mix-in class to change the appearance of a dialog, by handling WM_CTLCOLOR* messages, with five lines of code |
VC6, Windows, WTL, Dev
|
|
Advanced Search |
|
|
|
||||||||||||||||
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.
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:
HDC) for the
relevant control in their wParam .
HWND) in
their lParam.
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.
Five lines of code, that's all it takes:
CCtlColor.h).
CCtlColored<>) to your inheritance list,
with whatever flags you consider relevant.
COLOR_WINDOW and COLOR_WINDOWTEXT are not suitable for
your purpose. 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; } };
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.
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.
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.
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?
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.
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 respond to the WM_CTLCOLORSCROLLBAR message only if
they are direct children of your dialog: according to MSDN,
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. 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. WS_SCROLL, WS_VSCROLL), I'll be glad to learn.
Buttons do not respond to whatever settings you used while handling
WM_CTLCOLORBTN, unless they're owner-drawn.
On owner-drawn buttons, quote MSDN:
WM_INITDIALOGWM_COMMANDWM_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."
(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 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.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 3 May 2004 Editor: Nishant Sivakumar |
Copyright 2004 by Pablo Aliskevicius Everything else Copyright © CodeProject, 1999-2009 Web19 | Advertise on the Code Project |