I know from this answer that the WM_CTLCOLORSTATIC
message goes to the parent window of Dialog controls.
Regarding the parent window, if I put it in the direct Dialog Proc of a dialog I create below with DialogBox
, it works correctly.
But if I subclass the Dialog and move all the CTLCOLOR
code there instead of the direct proc, then WM_CTLCOLORSTATIC
stops working for me. WM_CTLCOLORDLG
still works. With this code, my Dialog is white but the Static labels are no longer white, they go back to gray. I'm still dealing with the Parent Window (Dialog), so it should work.
FULL REPRODUCIBLE EXAMPLE
DlgSubClassTest.cpp
#include "resource.h"
#include <Windows.h>
#include <CommCtrl.h>
#pragma comment(lib, "comctl32")
#pragma comment(linker, "\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
INT_PTR CALLBACK MyDlgProc(HWND hWndDlg, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK Subclassed_DialogMakeWhite(HWND hWndDlg, UINT message, WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ LPWSTR /*lpCmdLine*/,
_In_ int /*nCmdShow*/)
{
return (int)DialogBoxW(hInstance, MAKEINTRESOURCEW(IDD_TEST), nullptr, MyDlgProc);
}
// Dialog Proc
INT_PTR CALLBACK MyDlgProc(HWND hWndDlg, UINT message, WPARAM /*wParam*/, LPARAM /*lParam*/)
{
switch (message)
{
case WM_INITDIALOG:
SetWindowSubclass(hWndDlg, Subclassed_DialogMakeWhite, 0, 0);
return TRUE;
case WM_CLOSE:
EndDialog(hWndDlg, 0);
return TRUE;
default:
return FALSE;
}
}
// Subclass proc
LRESULT CALLBACK Subclassed_DialogMakeWhite(HWND hWndDlg, UINT message, WPARAM wParam, LPARAM lParam,
UINT_PTR /*uIdSubclass*/, DWORD_PTR /*dwRefData*/)
{
LRESULT lRes = DefSubclassProc(hWndDlg, message, wParam, lParam);
if (message == WM_CTLCOLORDLG)
{ // THIS WORKS
return (INT_PTR)GetStockObject(WHITE_BRUSH);
}
else if (message == WM_CTLCOLORSTATIC)
{ // THIS DOES NOT WORK
return (INT_PTR)GetStockObject(WHITE_BRUSH);
}
else
{
return lRes;
}
}
Dialog.rc
#include "resource.h"
#include <winres.h>
IDD_TEST DIALOGEX 0, 0, 173, 75
STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_SYSMENU
CAPTION "DlgSubClassTest"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
LTEXT "Static1",IDC_STATIC,20,25,29,12
LTEXT "Static2",IDC_STATIC,20,46,31,12
EDITTEXT IDC_EDIT1,70,24,82,15,ES_AUTOHSCROLL
EDITTEXT IDC_EDIT2,70,42,82,16,ES_AUTOHSCROLL
END
resource.h
#define IDD_TEST 100
#define IDC_EDIT1 1000
#define IDC_EDIT2 1001
You can use the following command script from a Visual Studio Command Prompt to build the executable.
build.cmd
@ECHO OFF
if not exist .\bin mkdir .\bin
if not exist .\obj mkdir .\obj
rc /nologo /fo.\obj\Dialog.res /l 409 Dialog.rc
cl /nologo /Fo.\obj\ /Fe.\bin\program.exe DlgSubClassTest.cpp /EHsc /std:c++17 /W4 /O2 /link Gdi32.lib User32.lib Dialog.res /LIBPATH:.\obj /MANIFEST:EMBED
Executing build && bin\program.exe
from a Visual Studio Command Prompt shows the following result:
The dialog's background is white (as expected), but the labels are rendered using the default background color (this is unexpected).
Moving the WM_CTLCOLORSTATIC
message handler implementation to MyDlgProc
produces the desired result:
How do I get the labels to render onto a white background while handling the WM_CTLCOLORSTATIC
message in a SUBCLASSPROC
(and why is the result different when handling the message in a DLGPROC
)?
The line of code that reads
// THIS DOES NOT WORK
isn't actually true. You can verify this by commenting out the WM_CTLCOLORSTATIC
branch and comparing the results:
The image on the right shows that the static controls' entire client areas are rendered using the default color, whereas the image on the left uses the custom brush for the background that isn't occupied by text.
This is the clue to derive a solution. Incidentally, the documentation for WM_CTLCOLORSTATIC
provides the instructions in plain text:
By responding to this message, the parent window can use the specified device context handle to set the text foreground and background colors of the static control.
The default foreground color is fine, but we need to set the background color to match the brush's color:
LRESULT CALLBACK Subclassed_DialogMakeWhite(HWND hWndDlg, UINT message, WPARAM wParam, LPARAM lParam,
UINT_PTR /*uIdSubclass*/, DWORD_PTR /*dwRefData*/)
{
LRESULT lRes = DefSubclassProc(hWndDlg, message, wParam, lParam);
if (message == WM_CTLCOLORDLG)
{
return (INT_PTR)GetStockObject(WHITE_BRUSH);
}
else if (message == WM_CTLCOLORSTATIC)
{
// Set the text background color
SetBkColor((HDC)wParam, RGB(255, 255, 255));
return (INT_PTR)GetStockObject(WHITE_BRUSH);
}
else
{
return lRes;
}
}
This produces the desired output:
Curiously, since you mentioned that you also get the desired output when handling the WM_CTLCOLORSTATIC
message in your dialog procedure, I investigated what's up with that. Indeed, adding a case WM_CTLCOLORSTATIC:
to MyDlgProc
somehow adjusts the text background color to match the background brush.
The documentation for WM_CTLCOLORSTATIC
doesn't describe this behavior and I want to believe that this hasn't always been the case. Regardless, I wanted to understand why the (undocumented) behavior was different between the two dialog procedures. As it turned out, the unconditional/early call to DefSubclassProc
seems to interfere with the automatic text background color adjustments.
The following implementation produces the same output (even without setting the text background color explicitly):
LRESULT CALLBACK Subclassed_DialogMakeWhite(HWND hWndDlg, UINT message, WPARAM wParam, LPARAM lParam,
UINT_PTR /*uIdSubclass*/, DWORD_PTR /*dwRefData*/)
{
switch (message)
{
case WM_CTLCOLORDLG:
return (INT_PTR)GetStockObject(WHITE_BRUSH);
case WM_CTLCOLORSTATIC:
return (INT_PTR)GetStockObject(WHITE_BRUSH);
default:
return DefSubclassProc(hWndDlg, message, wParam, lParam);
}
}
This is relying on undocumented behavior and I would suggest setting the text background color explicitly regardless.
Addendum: One could accomplish the same effect by calling SetBkMode
with a value of TRANSPARENT
instead of setting the text background color. This may seem attractive as it avoids the opportunity of failing to match colors. It is, however, difficult to coordinate with the control's default rendering to arrange for the text background to be rendered as expected. Plus, it affects other rendering operations (such as line drawing) and is computationally more expensive.
Therefore, always prefer SetBkColor
over SetBkMode
unless you have a very specific reason to use the latter.