c++winapiwindowgdidwm

Can't draw on custom window frame with DWM


I have created a custom window frame using DWM. The frame extends successfully but whenever I try to draw onto the frame, the extended frame coveres whatever I am trying to draw. I have seen other people try to input a top left within negative bounds, but even when I try to do that, the title bar still overlaps the main window's painting. Here is my code (note: i don't have any code for hit testing):

#include <Windows.h>
#include <numeric>
#include <dwmapi.h>
#pragma comment(lib, "dwmapi.lib")

const auto s_brush = CreateSolidBrush(RGB(0, 0, 255));

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    LRESULT res;
    if (DwmDefWindowProc(hwnd, msg, wparam, lparam, &res))
        return res;

    switch (msg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_CREATE:
    {
        RECT r;
        GetWindowRect(hwnd, &r);
        SetWindowPos(hwnd, 0, 0, 0, r.right - r.left, r.bottom - r.top, SWP_FRAMECHANGED);
    }
    break;
    case WM_ACTIVATE:
    {
        int metrics[4];
        const auto window_dpi_ = GetDpiForWindow(hwnd);

        metrics[0] = GetSystemMetricsForDpi(SM_CYCAPTION, window_dpi_);
        metrics[1] = GetSystemMetricsForDpi(SM_CXFIXEDFRAME, window_dpi_);
        metrics[2] = GetSystemMetricsForDpi(SM_CYSIZEFRAME, window_dpi_);
        metrics[3] = GetSystemMetricsForDpi(SM_CYBORDER, window_dpi_);

        const auto cy_titlebar_ = std::accumulate(metrics, metrics + sizeof metrics / sizeof(int), 0);
        MARGINS margins{ 0, 0, cy_titlebar_, 0 };
        DwmExtendFrameIntoClientArea(hwnd, &margins);
    }
    break;
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        const auto hdc = BeginPaint(hwnd, &ps);
        const auto old = SelectObject(hdc, s_brush);
        Rectangle(hdc, 0, 0, 50, 75);
        SelectObject(hdc, old);
        EndPaint(hwnd, &ps);
    }
    break;
    case WM_NCCALCSIZE:
        if (wparam == TRUE)
        {
            RECT& client_rect = reinterpret_cast<LPNCCALCSIZE_PARAMS>(lparam)->rgrc[0];
            const auto window_dpi_ = GetDpiForWindow(hwnd);
            const auto frame_width{ GetSystemMetricsForDpi(SM_CXFRAME, window_dpi_) };
            const auto border_width{ GetSystemMetricsForDpi(SM_CXPADDEDBORDER, window_dpi_) };
            const auto frame_height{ GetSystemMetricsForDpi(SM_CYFRAME, window_dpi_) };

            client_rect.bottom -= frame_height + border_width;
            client_rect.left += frame_width + border_width;
            client_rect.right -= frame_width + border_width;

            break;
        }
    default:
        return DefWindowProcW(hwnd, msg, wparam, lparam);
    }

    return 0;
}

int WINAPI wWinMain(HINSTANCE hinstance, HINSTANCE, LPWSTR lpcmdline, int cmd_show)
{
    WNDCLASS wc{ CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hinstance,
    0,0, reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)), 0, L"CustWnd" };

    const auto hwnd = CreateWindow(MAKEINTATOM(RegisterClass(&wc)), L"Custom Window Frame", WS_OVERLAPPEDWINDOW,
        0, 0, 500, 700, 0, 0, hinstance, 0);

    ShowWindow(hwnd, cmd_show);
    UpdateWindow(hwnd);

    MSG msg;

    while (GetMessageW(&msg, 0, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }

    return 0;
}

Solution

  • The window class doesn't have a default cursor, it's going to show the wrong cursors as you move the mouse. Change wc to

    WNDCLASS wc{ CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hinstance, 0, 
        LoadCursor(NULL, IDC_ARROW), 
        reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)), 0, L"CustWnd" };
    

    WM_NCHITTEST should also be handled, otherwise title-bar will not grip. It is better to calculate the border thickness based on Windows style, or keep it as static value because it will be needed throughout the procedure, as well as title bar height.

    Note that this code will look very different in Windows 10 versus Window 7 which has the weird transparent title-bar, you'll need 32-bit bitmap with alpha channel to draw on title-bar. Or use buffered paint with BufferedPaintSetAlpha as shown below

    #include <Windows.h> 
    #include <Windowsx.h> //for `GET_X_LPARAM` etc.
    ...
    LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        static int cy_titlebar_ = 100;
        static RECT border_thickness;
    
        LRESULT result;
        if(DwmDefWindowProc(hWnd, msg, wParam, lParam, &result))
            return result;
    
        switch(msg)
        {
        case WM_CREATE:
        {
            //find border thickness
            border_thickness = { 0 };
            if(GetWindowLongPtr(hWnd, GWL_STYLE) & WS_THICKFRAME)
            {
                AdjustWindowRectEx(&border_thickness,
                    GetWindowLongPtr(hWnd, GWL_STYLE) & ~WS_CAPTION, FALSE, NULL);
                border_thickness.left *= -1;
                border_thickness.top *= -1;
            }
            else if(GetWindowLongPtr(hWnd, GWL_STYLE) & WS_BORDER)
            {
                border_thickness = { 1,1,1,1 };
            }
    
            MARGINS margins = { 0, 0, cy_titlebar_, 0 };
            DwmExtendFrameIntoClientArea(hWnd, &margins);
            SetWindowPos(hWnd, NULL, 0, 0, 0, 0, 
                        SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
            return 0;
        }
    
        case WM_NCCALCSIZE:
        {
            if(wParam)
            {
                RECT& r = reinterpret_cast<LPNCCALCSIZE_PARAMS>(lParam)->rgrc[0];
                r.left += border_thickness.left;
                r.right -= border_thickness.right;
                r.bottom -= border_thickness.bottom;
                return 0;
            }
            break;
        }
    
        case WM_NCHITTEST:
        {
            result = DefWindowProc(hWnd, msg, wParam, lParam);
            if(result == HTCLIENT)
            {
                POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
                ScreenToClient(hWnd, &pt);
                if(pt.y < border_thickness.top) return HTTOP;
                if(pt.y < cy_titlebar_)  return HTCAPTION;
            }
            return result;
        }
    
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            auto hdc = BeginPaint(hWnd, &ps);
    
            //paint opaque:
            RECT rc{ 0, 0, 100, cy_titlebar_ };
            BP_PAINTPARAMS params = { sizeof(params), BPPF_NOCLIP | BPPF_ERASE };
            HDC memdc;
            HPAINTBUFFER hbuffer = BeginBufferedPaint(
                        hdc, &rc, BPBF_TOPDOWNDIB, &params, &memdc);
    
            auto brush = CreateSolidBrush(RGB(255, 0, 0));
            FillRect(memdc, &rc, brush);
            DeleteObject(brush);
    
            SetBkMode(memdc, TRANSPARENT);
            DrawText(memdc, L"Opaque", -1, &rc, 0);
            BufferedPaintSetAlpha(hbuffer, &rc, 255);
            EndBufferedPaint(hbuffer, TRUE);
    
            EndPaint(hWnd, &ps);
            return 0;
        }
    
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        }
    
        return DefWindowProc(hWnd, msg, wParam, lParam);
    }