9

INTRODUCTION AND RELEVANT INFORMATION:

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.

THE PROBLEM:

On Windows XP, here is the result image for first dialog:

enter image description here

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:

enter image description here

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.

WHAT HAVE I TRIED SO FAR:

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".

THE QUESTIONS:

  1. How can I modify my WM_CTLCOLORSTATIC handler to fix my problem ?

  2. 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.

EDITED WITH SUBMITTED EXAMPLE PROJECT:

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.

END OF EDIT

Thank you.

Best regards.

AlwaysLearningNewStuff
  • 2,939
  • 3
  • 31
  • 84
  • 1
    Can you prepare a [SSCCE](http://www.sscce.org/)? That is, prepare the smallest example demonstrating the problem, where all the code is contained in a single file - let's say main.cpp, that I could copy&paste into a new project and run without modifying, and it would reproduce the problem? – sashoalm Mar 22 '14 at 10:23
  • @sashoalm: I have edited my post with minimal code to recreate the problem. Best regards. – AlwaysLearningNewStuff Mar 22 '14 at 19:37
  • @valter: I use `CreateDialogParam(..)`, but I have edited my post to include *SSCCE* and there is used `DialogBox(...)` API. Best regards. – AlwaysLearningNewStuff Mar 22 '14 at 19:38
  • You can not change the groupbox frame. You have to draw it your self. If you are not going to resize your dialog you can achieve what you want by drawing everything yourself. It is not difficult because your dialog is very simple. Draw your background, text and frame in the WM_PAINT of dialog. Add regular buttons instead of check and radio buttons and draw the images in various events(enter,down,leave). This is the way I always do it. If you are interested I will give you code. – γηράσκω δ' αεί πολλά διδασκόμε Mar 22 '14 at 22:05
  • @valter: Thank you for your suggestions. I will try to find an alternative way for now, since this sounds like a lot of work. If I "hit the wall" then your option will indeed be the only way. In that case I will leave a comment asking for the example code snippets. Thank you kindly, I highly appreciate it. Hopefully this problem will be solved in a way we can both benefit from. Best regards. – AlwaysLearningNewStuff Mar 22 '14 at 22:16
  • What about adding `WS_EX_TRANSPARENT` style to the controls to have their transparent parts painted for you instead of doing it yourself? – Roman R. Mar 27 '14 at 13:53
  • @RomanR.: I have tried adding this style in `CreateWindowEx()` but checkbox' text and background were black. I do not know what you had in mind. A pseudo code, or instructions might put me on the right track. Thank you for trying to help. Best regards. – AlwaysLearningNewStuff Mar 27 '14 at 14:01
  • You can simply add the style into `.rc` script, just keep in mind it's "ex" style, not the regular `WS_xxx` style (different set of styles). There is no project for quick checking, so I am not sure what could go wrong when you tried it out. – Roman R. Mar 27 '14 at 14:20
  • I have checked `transparent` option in `.rc` editor. After seeing the code for the `.rc` file by selecting `View Source...` I can confirm that button has `WS_EX_TRANSPARENT` style added. Still I get the same result as in the picture I posted. It was tested on Windows XP. Best regards. – AlwaysLearningNewStuff Mar 27 '14 at 14:25

3 Answers3

3

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

  • Thank you for answering. My radio buttons and check boxes have `BS_AUTORADIOBUTTON` and `BS_AUTOCHECKBOX` styles set. In your first code snippet, in line `hBitmapOld = SelectObject(hdcMemBoardImage, hBitmap);` did you make a typo, shouldn't there be `hBitmapOld = SelectObject(hdcMemDialogBackground, hBitmap);` ? I also assume that the first code snippet is in `WM_INITDIALOG` and that `hdcMemDialogBackground` and `hBitmap` are `static`, so I ask this: How to handle resizing of a dialog? Bitmap's dimensions can be adjusted in `WM_SIZE`, but what to do with memory DC ? Thank you. Best regards. – AlwaysLearningNewStuff Mar 25 '14 at 22:28
  • @AlwaysLearningNewStuff You are right about the typo. I used some old code and forgot to correct it. The resize needs some thought. I will post an edit if i think of something. – γηράσκω δ' αεί πολλά διδασκόμε Mar 25 '14 at 22:41
  • I was unable to implement your code properly ( the part for `WM_NOTIFY` ). The coordinates of the radio button are the problem. Can you help me with an edit that shows how you handled the coordinates for the source DC ( `hdcMemDialogBackground` ) ? Thank you. Best regards. – AlwaysLearningNewStuff Mar 25 '14 at 23:55
  • I have awarded you the full bounty since the timer would expire in few hours and I doubt anyone else would post an answer. Although I like your answer, I have found a way to solve this with custom draw. I will try to solve my problem this way, and then I will try your way. At the moment I did not accept your answer because if custom draw succeeds it will suit me better. Still, I highly appreciate your efforts, hence the awarded bounty and I have upvoted your answer. Thank you for your help and effort. Best regards. – AlwaysLearningNewStuff Mar 28 '14 at 19:00
2

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.

Dmitry Sokolov
  • 3,118
  • 1
  • 30
  • 35
  • MSDN remarks: *The text color of a check box or radio button applies to the box or button, its check mark, and the text. **The focus rectangle for these buttons remains the system default color (typically black)***. At the very end of the thread it was indicated that offered solution was incorrect. After downloading the project and changing button style from push button to radio button, I still had its text and text background color to be system default! Can you can prove that your solution works by providing a working example ( I do not believe this will ever work ) ? Best regards. – AlwaysLearningNewStuff Mar 22 '14 at 20:31
  • I was wrong. The exmple works only for push buttons. – Dmitry Sokolov Mar 22 '14 at 22:45
  • Did you see http://stackoverflow.com/questions/7333685/transparent-radio-button-control-with-themes-using-win32 ? – Dmitry Sokolov Mar 23 '14 at 13:58
  • Yes I have seen it, but it owner draws the entire control. I believe that my problem can be solved in a simpler way using `Visual Styles` API. Thank you. Best regards. – AlwaysLearningNewStuff Mar 23 '14 at 16:50
0

I had this issue where the background of the checkbox was black or not drawing correctly. For me, WM_ERASEBKGND was causing this issue. Instead of returning true or non-zero, i removed the message handler all together. As the accepted answer says, draw the background in the WM_PAINT message.

I just wanted to elaborate on what was causing the issue.

EDIT:

Another possible solution if removing the handler causes flickering is to check what window is sending the WM_ERASEBKGND message and only return non-zero if its from the parent window.

case WM_ERASEBKGND:
{
    if (hWnd == WindowFromDC((HDC)wParam))
    {
        return 1;
    }
    break;
}

I assume this could be used to change the checkbox background color or even make it transparent.

b.sullender
  • 163
  • 1
  • 16