Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WM_EX_TRANSPARENT doesn't repaint child windows

I am using the WM_EX_TRANSPARENT window style on some windows in an attempt to do double-buffering with transparency. However, I am having a problem because when I do InvalidateRect on the parent window, the child windows are not redrawn.

Is it the parent window's responsibility to iterate the child windows and get them to repaint themselves, or am I doing it wrong? If it's the parent window's responsibility, how can I get all the child windows within the invalid rectangle of the parent?

Here is a fully compilable example that illustrates the behaviour I'm talking about. You can see that the button disappears when you move your mouse over the parent window. The button redraws itself if you click on it, but disappears again when you mouse off of it and back on to the parent window.

#include <Windows.h>
    
LRESULT CALLBACK WndProc(HWND window, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
    case WM_CREATE:
        return 0;
    
    case WM_MOUSEMOVE:
        InvalidateRect(window, NULL, true);
    
        return 0;
    
    case WM_NCDESTROY:
        PostQuitMessage(0);
        break;
    }
    
    return DefWindowProc(window, uMsg, wParam, lParam);
}
    
int PASCAL WinMain(HINSTANCE hinst, HINSTANCE, LPSTR, int nShowCmd) {
    WNDCLASS wc;
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hinst;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = "test_window";
    
    RegisterClass(&wc);
    
    HWND wnd = CreateWindowEx(WS_EX_TRANSPARENT, "test_window", "test", WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW, 50, 50, 400, 400, NULL, NULL, hinst, NULL);
    
    ShowWindow(wnd, SW_SHOW);
    
    HWND btn = CreateWindowEx(WS_EX_TRANSPARENT, "BUTTON", "button 1", WS_CHILD, 50, 50, 100, 50, wnd, NULL, hinst, NULL);
    
    ShowWindow(btn, SW_SHOW);
    
    MSG m;
    
    while (GetMessage(&m, NULL, 0, 0)) {
        TranslateMessage(&m);
        DispatchMessage(&m);
    }
    
    return 0;
}

The final solution

was to do as the answer below suggested, and then in the parent window's WM_PAINT message send a WM_PAINT to each child and have the children paint into the parent's doublebuffer (not painting anything into itself) and then have the parent BitBlt it's buffer (that has been drawn into by itself and then by each of its children going from the bottom to the top of the Z-order) into it's HDC. That allows flicker-free drawing in the parent and child, and transparency for the child. Here is the final demo program we arrived at:

#include <Windows.h>

// the handle to the single child window
HWND child;

// parent's backbuffer so we can use it in the child
HDC bbuf = 0;
        
LRESULT CALLBACK WndProc(HWND window, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
    case WM_CREATE:
        return 0;
        
    case WM_MOUSEMOVE:
        InvalidateRect(window, NULL, true);
        
        return 0;
    
    case WM_PAINT: {
        PAINTSTRUCT ps;
        POINT mpos;
        GetCursorPos(&mpos);
        ScreenToClient(window, &mpos);
    
        BeginPaint(window, &ps);
        
        // create the backbuffer once
        bbuf = bbuf ? bbuf : CreateCompatibleDC(ps.hdc);
    
        // hardcoded size is the same in the CreateWindowEx call
        static auto backbmp = CreateCompatibleBitmap(ps.hdc, 400, 400);
    
        static auto unused = SelectObject(bbuf, backbmp);
    
        POINT points[2] = {
            { 0, 0 },
            { mpos.x, mpos.y }
        };
    
        // painting into bbuf
        // give ourselves a white background so we can see the wblack line
        SelectObject(bbuf, (HBRUSH)GetStockObject(WHITE_BRUSH));
        Rectangle(bbuf, 0, 0, 400, 400);
        SelectObject(bbuf, (HBRUSH)GetStockObject(BLACK_BRUSH));
        Polyline(bbuf, points, 2);

        // get the child to paint itself into our bbuf
        SendMessage(child, WM_PAINT, 0, 0);
    
        // and after the child has drawn, bitblt everything onto the screen
        BitBlt(ps.hdc, 0, 0, 400, 400, bbuf, 0, 0, SRCCOPY);
    
        EndPaint(window, &ps);
    
        return 0;
    }

    case WM_ERASEBKGND:
        return 0;
        
    case WM_NCDESTROY:
        PostQuitMessage(0);
        break;
    }
        
    return DefWindowProc(window, uMsg, wParam, lParam);
}
    
LRESULT CALLBACK ChildWndProc(HWND window, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
    case WM_CREATE:
        return 0;
    
    case WM_PAINT: {
        PAINTSTRUCT ps;
    
        BeginPaint(window, &ps);

        static auto backbuffer = CreateCompatibleDC(ps.hdc);
        static auto backbmp = CreateCompatibleBitmap(ps.hdc, 100, 50);

        static auto unused = SelectObject(backbuffer, backbmp);

        // copy the parent's stuff into our backbuffer (the parent has already drawn)
        BitBlt(backbuffer, 0, 0, 100, 50, bbuf, 50, 150, SRCCOPY);

        RECT r = { 0, 0, 50, 100 };

        // draw into our backbuffer
        SetBkMode(backbuffer, TRANSPARENT);
        SetTextColor(backbuffer, RGB(255, 0, 0));
        DrawText(backbuffer, "hello", 5, &r, DT_NOCLIP | DT_TABSTOP | DT_EXPANDTABS | DT_NOPREFIX);

        // bitblt our stuff into the parent's backbuffer
        BitBlt(bbuf, 50, 150, 100, 50, backbuffer, 0, 0, SRCCOPY);
    
        EndPaint(window, &ps);
    
        return 0;
    }
    
    case WM_ERASEBKGND:
        return 0;
    }
        
    return DefWindowProc(window, uMsg, wParam, lParam);
}
    
int PASCAL WinMain(HINSTANCE hinst, HINSTANCE, LPSTR, int nShowCmd) {
    WNDCLASS wc;
    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hinst;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = "test_window";
        
    RegisterClass(&wc);
    
    wc.style         = 0;
    wc.lpfnWndProc   = ChildWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hinst;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = "transparent_window";
        
    RegisterClass(&wc);
        
    HWND wnd = CreateWindowEx(NULL, "test_window", "test", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 50, 50, 400, 400, NULL, NULL, hinst, NULL);
        
    child = CreateWindowEx(NULL, "transparent_window", "", WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_CHILD, 50, 150, 100, 50, wnd, NULL, hinst, NULL);
    
    MSG m;
    
    while (GetMessage(&m, NULL, 0, 0)) {
        TranslateMessage(&m);
        DispatchMessage(&m);
    }
        
    return 0;
}

Thanks again to johnathon for spending so much time helping me figure this out.

like image 253
Seth Carnegie Avatar asked Nov 05 '22 00:11

Seth Carnegie


1 Answers

remove WS_CLIPCHILDREN from your parent windows window style. This will allow the parent window to paint over the real estate of the child window, then the child window will effectively paint over that during it's paint call. The parent window's paint gets called first, then any child windows get their paint called. Good luck Seth!

like image 84
johnathan Avatar answered Nov 15 '22 06:11

johnathan