I have 2 dialog boxes created via Resource editor. Since I use Microsoft Visual Studio Express edition, I had to download free resource editor to create them. In my program, I have Visual Styles
enabled like this:
#include <commctrl.h>
#pragma comment( lib, "comctl32.lib")
#pragma comment( linker, "/manifestdependency:\"type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
language='*'\"")
As far as I know, check box, radio button, and group box get WM_CTLCOLORSTATIC
message for painting their text.
This is what I have coded for the first and second dialog box:
case WM_CTLCOLORSTATIC:
{
SetBkMode( (HDC)wParam, TRANSPARENT );
SetTextColor( (HDC)wParam, RGB( 0, 0, 0 ) );
return (INT_PTR)( (HBRUSH)GetStockObject(NULL_BRUSH) );
}
I want those controls to have transparent text background and black color of their text.
On Windows XP, here is the result image for first dialog:
Group box has blue text, and brown border, while check box has everything black. On Windows 7, after starting the same program, I get this:
Here, group box and check box have proper text color, yet background of the check box and the border of the group box are wrong. In my dialog box, I have static controls and they are painted properly both on Windows 7 and Windows XP.
I have browsed through SO archive, but haven't found anything I could use to modify my WM_CTLCOLORSTATIC
handler.
I have found a workaround which removes Visual Styles from those controls, so it can achieve the desired result, but I need to keep the Visual Styles and make the background of the text transparent, thus this solution can not satisfy me.
After looking through Visual Styles reference and a little experimenting, I have found a workaround for radio button and check box ( but not for group box ) with the following code:
case WM_CTLCOLORSTATIC:
if( (HWND)lParam == GetDlgItem( hwnd, IDC_RADIO1 ) )
{
RECT r;
GetClientRect( hwnd, &r );
DrawThemeParentBackground( (HWND)lParam, (HDC)wParam, &r );
}
else
{
SetTextColor( (HDC)wParam, RGB( 0, 0, 0 ) );
SetBkMode( (HDC)wParam, TRANSPARENT );
}
return (INT_PTR)( (HBRUSH)GetStockObject(NULL_BRUSH) );
Still, I have "hit the wall" with this:
In my dialog box there is a treeview and once I select node and press spacebar ( or any other key for that matter ) dialog's background bitmap gets on top of my static controls.
After I comment out DrawThemeParentBackground()
, recompile and start program again everything works fine ( when I select tree's node and press spacebar ) but then I am "at square one".
How can I modify my WM_CTLCOLORSTATIC
handler to fix my problem ?
If the above is not possible, can I get the desired effect with NM_CUSTOMDRAW
?
NOTE:
I think that I will have to draw group box
using GDI
. If that is the case, I will accept this solution too, as my main concern is checkbox
and radio button
.
On request, I am submitting a SSCCE. To create the project follow these steps:
1.) Create default Win32 project in Visual Studio.
2.) In the stdafx.h
copy/paste the following directives, below #include <windows.h>
:
#include <commctrl.h>
#include <Uxtheme.h>
#pragma comment( linker, "/manifestdependency:\"type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
language='*'\"")
#pragma comment( lib, "comctl32.lib")
#pragma comment( lib,"Msimg32.lib") // needed for GradientFill(...) API
#pragma comment( lib, "UxTheme.lib")
3.) In the resource editor, alter About dialog box by adding a 2 radio buttons, 2 checkboxes and a group box enclosing them ( see the above pictures too see what I mean ) and add a treeview control ( this control will "trigger" the problem ).
4.) In your main cpp
file, add the helper function for drawing the custom background:
void GradientTriangle( HDC MemDC,
LONG x1, LONG y1,
LONG x2, LONG y2,
LONG x3, LONG y3,
COLORREF top, COLORREF bottom )
{
TRIVERTEX vertex[3];
vertex[0].x = x1;
vertex[0].y = y1;
vertex[0].Red = GetRValue(bottom) << 8;
vertex[0].Green = GetGValue(bottom) << 8;
vertex[0].Blue = GetBValue(bottom) << 8;
vertex[0].Alpha = 0x0000;
vertex[1].x = x3;
vertex[1].y = y3;
vertex[1].Red = GetRValue(bottom) << 8;
vertex[1].Green = GetGValue(bottom) << 8;
vertex[1].Blue = GetBValue(bottom) << 8;
vertex[1].Alpha = 0x0000;
vertex[2].x = x2;
vertex[2].y = y2;
vertex[2].Red = GetRValue(top) << 8;
vertex[2].Green = GetGValue(top) << 8;
vertex[2].Blue = GetBValue(top) << 8;
vertex[2].Alpha = 0x0000;
// Create a GRADIENT_TRIANGLE structure that
// references the TRIVERTEX vertices.
GRADIENT_TRIANGLE gTriangle;
gTriangle.Vertex1 = 0;
gTriangle.Vertex2 = 1;
gTriangle.Vertex3 = 2;
// Draw a shaded triangle.
GradientFill( MemDC, vertex, 3, &gTriangle, 1, GRADIENT_FILL_TRIANGLE);
}
5.) Initiate common controls
in _tWinMain
:
// initialize common controls
INITCOMMONCONTROLSEX iccex;
iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
iccex.dwICC = ICC_LISTVIEW_CLASSES | ICC_UPDOWN_CLASS | ICC_STANDARD_CLASSES ;
InitCommonControlsEx(&iccex);
6.) Insert some items in the treeview in the WM_INITDIALOG
:
case WM_INITDIALOG:
{
HWND TreeView = GetDlgItem( hDlg, IDC_TREE1 );
// add root item
TVINSERTSTRUCT tvis = {0};
tvis.item.mask = TVIF_TEXT;
tvis.item.pszText = L"This is root item";
tvis.hInsertAfter = TVI_LAST;
tvis.hParent = TVI_ROOT;
HTREEITEM hRootItem = reinterpret_cast<HTREEITEM>( SendMessage( TreeView ,
TVM_INSERTITEM, 0, reinterpret_cast<LPARAM>( &tvis ) ) );
// add firts subitem for the hTreeItem
memset( &tvis, 0, sizeof(TVINSERTSTRUCT) );
tvis.item.mask = TVIF_TEXT;
tvis.item.pszText = L"This is first subitem";
tvis.hInsertAfter = TVI_LAST;
tvis.hParent = hRootItem;
HTREEITEM hTreeSubItem1 = reinterpret_cast<HTREEITEM>( SendMessage( TreeView ,
TVM_INSERTITEM, 0, reinterpret_cast<LPARAM>( &tvis ) ) );
// now we insert second subitem for hRootItem
memset( &tvis, 0, sizeof(TVINSERTSTRUCT) );
tvis.item.mask = TVIF_TEXT | TVIF_STATE; // added extra flag
tvis.item.pszText = L"This is second subitem";
tvis.hInsertAfter = TVI_LAST;
tvis.hParent = hRootItem;
HTREEITEM hTreeSubItem2 = reinterpret_cast<HTREEITEM>( SendMessage( TreeView ,
TVM_INSERTITEM, 0, reinterpret_cast<LPARAM>( &tvis ) ) );
}
return (INT_PTR)TRUE;
7.) Paint the custom background of the dialog:
case WM_ERASEBKGND:
{
RECT r;
GetClientRect( hDlg, &r );
GradientTriangle( (HDC)wParam, r.right, r.bottom - r.top,
r.left, r.bottom - r.top,
r.left, r.top,
RGB( 0x0, 0x0, 0xFF ), RGB( 0xFF, 0xFF, 0x0 ) );
GradientTriangle( (HDC)wParam, r.right, r.bottom - r.top,
r.right, r.top,
r.left, r.top,
RGB( 0xFF, 0x0, 0x0 ), RGB( 0x0, 0xFF, 0x0 ) );
}
return TRUE;
8.) Add the handler for WM_CTLCOLORSTATIC
:
case WM_CTLCOLORSTATIC:
if( ( (HWND)lParam == GetDlgItem( hDlg, IDC_RADIO1 ) )
|| ( (HWND)lParam == GetDlgItem( hDlg, IDC_CHECK1 ) ) )
{
RECT r;
GetClientRect( hDlg, &r );
DrawThemeParentBackground( (HWND)lParam, (HDC)wParam, &r );
}
else
{
SetTextColor( (HDC)wParam, RGB( 0, 0, 0 ) );
SetBkMode( (HDC)wParam, TRANSPARENT );
}
return (INT_PTR)( (HBRUSH)GetStockObject(NULL_BRUSH) );
That would be all required steps and information to create minimal program that demonstrates the problem. Now run the application and observe the differences:
Radio button with IDC_RADIO1
and checkbox with IDC_CHECK1
will be properly painted ( transparent text with black color ), while other checkbox and radio button will not. Furthermore, after you click on a treeview and press spacebar you will see the problem with dialogs background.
Thank you.
Best regards.
I have made your dialog and run it in both xp and 7 and everything went fine. What you should do is the following:
Create a bitmap in memory and do all your drawings there. This should be the dialog background.
HDC hdc = CreateIC(TEXT("DISPLAY"), NULL, NULL, NULL);
hdcMemDialogBackground = CreateCompatibleDC(hdc);
hBitmap = CreateCompatibleBitmap(hdc, dialogWidth, dialogHeight);
hBitmapOld = SelectObject(hdcMemDialogBackground , hBitmap);
DeleteDC(hdc);
For creating radiobutton and checkbox use "button" class not static with
WS_VISIBLE | WS_CHILD | BS_AUTOCHECKBOX
WS_VISIBLE | WS_CHILD | BS_AUTORADIOBUTTON
styles.
In your dialog you should include
WS_CLIPCHILDREN
style. This will solve you the background overlaping the controls upon space press.
You should not draw the background of dialog in WM_ERASEBKGND
message but WM_PAINT
case WM_PAINT:
RECT r, rt;
GetUpdateRect(hDlg, &rt, false);
GetClientRect( hDlg, &r );
GradientTriangle( hdcMemDialogBackground, r.right, r.bottom - r.top,
r.left, r.bottom - r.top,
r.left, r.top,
RGB( 0x0, 0x0, 0xFF ), RGB( 0xFF, 0xFF, 0x0 ) );
GradientTriangle( hdcMemDialogBackground, r.right, r.bottom - r.top,
r.right, r.top,
r.left, r.top,
RGB( 0xFF, 0x0, 0x0 ), RGB( 0x0, 0xFF, 0x0 ) );
hdc = BeginPaint(hDlg, &ps);
BitBlt(hdc, rt.left, rt.top, rt.right - rt.left, rt.bottom - rt.top, hdcMemDialogBackground, rt.left, rt.top, SRCCOPY);
EndPaint(hDlg, &ps);
break;
And finaly for the transparent background, in WM_NOTIFY
:
case WM_NOTIFY:
NMHDR *nmr;
NMCUSTOMDRAW *nmcd;
nmr = (NMHDR *)lParam;
nmcd = (NMCUSTOMDRAW *)lParam;
if(nmr->idFrom == IDC_RADIO1 && nmr->code == NM_CUSTOMDRAW){
if(nmcd->dwDrawStage == CDDS_PREERASE){
BitBlt(nmcd->hdc, 0, 0, radioButton1Width, radioButton1Height, hdcMemDialogBackground, radioButton1PosX, radioButton1PosY, SRCCOPY);
return CDRF_SKIPDEFAULT;
}
}
if(nmr->idFrom == IDC_RADIO2 && nmr->code == NM_CUSTOMDRAW){
//the same
}
if(nmr->idFrom == IDC_CHECK1 && nmr->code == NM_CUSTOMDRAW){
//the same
}
if(nmr->idFrom == IDC_CHECK2 && nmr->code == NM_CUSTOMDRAW){
//the same
}
break;
In the end dispose your resources:
SelectObject(hdcMemDialogBackground, hBitmapOld);
DeleteObject(hBitmap);
hBitmap = NULL;
DeleteDC(hdcMemDialogBackground);
hdcMemDialogBackground= NULL;
EDIT
POINT pt;
RECT rt;
GetClientRect(hwndRadioButton1, &rt);
pt.x = 0;
pt.y = 0;
ClientToScreen(hwndRadioButton1, &pt);
ScreenToClient(hDlg, &pt);
BitBlt(nmcd->hdc, 0, 0, rt.right, rt.bottom, hdcMemDialogBackground, pt.x, pt.y, SRCCOPY);
EDIT2 (for resizing)
Change WM_PAINT:
case WM_PAINT:
GetUpdateRect(hDlg, &rt, false);
hdc = BeginPaint(hDlg, &ps);
BitBlt(hdc, rt.left, rt.top, rt.right - rt.left, rt.bottom - rt.top, hdcMemDialogBackground, rt.left, rt.top, SRCCOPY);
EndPaint(hDlg, &ps);
break;
In WM_SIZE:
case WM_SIZE:
r.left = 0;
r.top = 0;
r.right = LOWORD(lParam);;
r.bottom = HIWORD(lParam);
GradientTriangle(hdcMemDialogBackground, r.right, r.bottom - r.top,
r.left, r.bottom - r.top,
r.left, r.top,
RGB( 0x0, 0x0, 0xFF ), RGB( 0xFF, 0xFF, 0x0 ) );
GradientTriangle(hdcMemDialogBackground, r.right, r.bottom - r.top,
r.right, r.top,
r.left, r.top,
RGB( 0xFF, 0x0, 0x0 ), RGB( 0x0, 0xFF, 0x0 ) );
InvalidateRect(hwndRadioButton1, NULL, false);
InvalidateRect(hwndRadioButton2, NULL, false);
InvalidateRect(hwndCheck11, NULL, false);
InvalidateRect(hwndCheck2, NULL, false);
InvalidateRect(hDlg, NULL, false);
break;
For the resizing to work you need to create hdcMemDialogBackground
with the maximum
dimensions dialogbox will have eg 1280x1024 or 1680x1050 or any other.
Thats it.
valter
In the WM_CTLCOLORSTATIC
handler you can only change BG of static controls.
Push buttons, radio buttons and check buttons are the same control - button. To change it BG you need to handle WM_CTLCOLORBTN
. An example may be found here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With