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?
Asked Answered
D

4

0

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!

Destruct answered 15/8, 2014 at 1:13 Comment(7)
The standard Win32 controls don't support transparency by themselves. You have to kludge it. WS_EX_TRANSPARENT is not the way.Cynosure
Correct, but here I was under the impression that if I set WS_EX_TRANSPARENT, which tells the window manager not to draw until parent windows have drawn, and return the hollow brush from the WM_CTLCOLORxxx messages, that the controls will draw with the hollow brush, which draws nothing, and thus shows the contents below. Where is this breaking?Destruct
Ah I see, it's actually transparent to siblings, not to the parent :| So I'm guessing checkboxes don't handle WM_CTLCOLORSTATIC properly, in which case is there anything else I can do to avoid having to draw a background myself? I tried case WM_ERASEKGND: return 1; in the subclass and calling SetBkMode() there, both to no effect.Destruct
For push buttons, try handling WM_PRINTCLIENT in your parent's handler, that helped me in the past. The point is to copy your drawing code from the WM_PAINT except you have now HDC provided as wParam of the message ( HDC hdc = (HDC)wParam instead of HDC hdc = BeginPaint and you don't need EndPaint). As for checkbox, I guess you will need to custom draw it as I don't know any other solution...Nariko
I'm not handling custom drawinat all; this is all the DefWindowProc() responses :SDestruct
I'm starting to think the behavior I want is a specific property of property sheets (which is lame), but I suppose that's progress?????Destruct
Update: I found this: blogs.msdn.com/b/oldnewthing/archive/2010/06/28/10031289.aspx - but using DefWindowProc() to do what it says here has no effect. I also gave up and looked into the comctl32 DLL itself, but I have no idea where it's drawing the black part, just that it draws whatever brush you return...Destruct
M
1

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;
}
Manvel answered 11/6, 2016 at 15:42 Comment(1)
This really worked! Thanks. I've looking for a way to make Command Links transparent, it worked. This answer should be accepted, this DOES WORK and it DOES NOT require too many lines of code.Farrington
D
0

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!

Destruct answered 28/8, 2014 at 14:41 Comment(0)
B
0

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);
       }

Buckboard answered 18/4, 2021 at 20:10 Comment(0)
D
0

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 */);
}
Delacroix answered 11/7, 2023 at 22:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.