andlabs
andlabs

Reputation: 11578

Once and for all: how do I get a fully transparent checkbox, button, radio button, etc. in Windows API, and not with a black background?

First, sorry if I sound arrogant/rude here.

All right, so everyone has run into this by now (I hope); I just haven't found any adequate answer anywhere. We start with a Common Controls 6 manifest and

case WM_CTLCOLORSTATIC:
    if (/* window has WS_EX_TRANSPARENT */) {
        SetBkMode((HDC) wParam, TRANSPARENT);
        return (LRESULT) GetStockObject(HOLLOW_BRUSH);
    }

and give our labels WS_EX_TRANSPARENT. They become transparent; so far so good. Now we have to add that style to our checkboxes (because checkboxes respond to that and not to WM_CTLCOLORBTN for some reason). And... the checkboxes become black!

Is there any way to make them fully transparent without resorting to owner draw? I'd rather not draw the checkboxes myself; I'd rather not have to guess whether it looks right or what the sizes are should the theming API fail on me (and I'm going to have to draw check boxes by themselves in the future when I add custom checkboxes to my list views and I'm already not happy with the amount of guessing involved).

These checkboxes are being drawn over a themed tab control. So far, I've found five dialogs in Windows XP with transparent checkboxes on themed tabs: General tab of Shortcut Properties, Taskbar tab of Taskbar and Start Menu Properties, System Restore tab of System Properties, General tab of Folder Options (radio buttons), and Keyboard tab of Accessibility Options. So this certainly must be possible! I'm sure the Windows UI authors didn't have to use custom draw throughout the OS... What are we all missing?

If I need to subclass that's fine (I already have a subclass anyway for event handling purposes), but I still would rather not have to draw myself.

As a bonus, what about push buttons? Overriding WM_CTLCOLORBTN gives buttons a black border, but I do notice that none of the standard dialogs mentioned above bother to make the corners of buttons transparent, so eh :/

Thanks!

Upvotes: 0

Views: 4485

Answers (4)

busdriverflix
busdriverflix

Reputation: 1

I don't know if this still helps anyone, but you could do this:

case WM_CTLCOLORSTATIC:
{
    return CreateSolidBrush(/* The background color of the parent window */);
}

Upvotes: 0

Ken_sf
Ken_sf

Reputation: 119

This answer is late, but offers a simpler approach.

To make a GroupBox's children transparent, process the WM_CTLCOLORBTN message in the GroupBox's subclass proc.

To make a GroupBox's title transparent, process the WM_CTLCOLORBTN message in the parent of the GroupBox's wndproc procedure.

Here is the link showing where I learned how.

My code (need this in both the groupbox's parent and in the groupbox's subclass proc):

case WM_CTLCOLORSTATIC:
 case WM_CTLCOLORBTN:{
    SetBkMode((HDC) wParam, TRANSPARENT);

//if you need to get the classname
//char class_name[100];
  // GetClassName(hwnd, class_Name, 100);  
//end if you need to get the classname

    WNDCLASS lpcls{};
    GetClassInfo(hInstance,  _T("MainWindow"),&lpcls);//or use class_name
    return  (LRESULT)lpcls.hbrBackground;//works everywhere bs_groupbox
//below works in the groupbox's subclass proc but not in parent (strikes through the title)
 // return (LRESULT)GetStockObject(NULL_BRUSH);
       }

Upvotes: 0

SaeidMo7
SaeidMo7

Reputation: 1304

You have to return the hbrBackground element of registered class of window as follow:

case WM_CTLCOLORBTN:
case WM_CTLCOLORSTATIC: {
    wchar_t  class_Name[100];
    GetClassName(hWnd, class_Name, 100);
    WNDCLASS lpcls{};
    GetClassInfo(g_hInstance, class_Name, &lpcls);

    return  (LRESULT)lpcls.hbrBackground;
}

Upvotes: 1

andlabs
andlabs

Reputation: 11578

Mostly nailed this:

void paintControlBackground(HWND hwnd, HDC dc)
{
    HWND parent;
    RECT r;
    POINT p;
    int saved;

    parent = GetParent(hwnd);
    if (parent == NULL)
        xpanic("error getting parent container of control in paintControlBackground()", GetLastError());
    if (GetWindowRect(hwnd, &r) == 0)
        xpanic("error getting control's window rect in paintControlBackground()", GetLastError());
    // the above is a window rect; convert to client rect
    p.x = r.left;
    p.y = r.top;
    if (ScreenToClient(parent, &p) == 0)
        xpanic("error getting client origin of control in paintControlBackground()", GetLastError());
    saved = SaveDC(dc);
    if (saved == 0)
        xpanic("error saving DC info in paintControlBackground()", GetLastError());
    if (SetWindowOrgEx(dc, p.x, p.y, NULL) == 0)
        xpanic("error moving window origin in paintControlBackground()", GetLastError());
    SendMessageW(parent, WM_PRINTCLIENT, (WPARAM) dc, PRF_CLIENT);
    if (RestoreDC(dc, saved) == 0)
        xpanic("error restoring DC info in paintControlBackground()", GetLastError());
}

    case WM_CTLCOLORSTATIC:
    case WM_CTLCOLORBTN:
        if (SetBkMode((HDC) wParam, TRANSPARENT) == 0)
            xpanic("error setting transparent background mode to Labels", GetLastError());
        paintControlBackground((HWND) lParam, (HDC) wParam);
        *lResult = (LRESULT) hollowBrush;
        return TRUE;

There are a few loose ends:

This won't work if the immediate parent of the given control is a groupbox; in that case, you'll need to inspect the class name and walk up the parent chain.

You also need to implement WM_PRINTCLIENT on any custom containers AND in the window class.

This doesn't yet work for groupbox labels; you'll see the texture drawn properly, but the groupbox line will be drawn on top of it. I'll mark this as solved when I figure that out and update this post with that info.

(The flicker I was seeing seems to be caused by a stop-the-world garbage collector used by another part of the program, and is thus out of my control for the time being. This will change soon, hopefully.)

Thanks in the meantime!

Upvotes: 0

Related Questions