Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Common controls are not properly painted with WM_CTLCOLORSTATIC handler after I set Visual Styles on

Tags:

c++

winapi

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.

like image 927
AlwaysLearningNewStuff Avatar asked Mar 20 '14 09:03

AlwaysLearningNewStuff


2 Answers

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.

like image 22
Dmitry Sokolov Avatar answered Nov 09 '22 19:11

Dmitry Sokolov