0

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;
}
Arush Agarampur
  • 1,340
  • 7
  • 20

1 Answers1

2

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);
}
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • I see what you did but the rectangle does still not show, it seems to be covered by the window frame. – Arush Agarampur Aug 11 '19 at 04:00
  • See the example with buffered paint for painting opaque color. – Barmak Shemirani Aug 11 '19 at 04:31
  • Nevermind, test on Windows 7 and the rectangle shows and works. Thank you. – Arush Agarampur Aug 11 '19 at 04:31
  • Hi, I have one question - if I want to change the color behind the caption buttons, like Windows Explorer on W10 when the dark theme is enabled? Because currently, the buffered paint will cover the caption buttons when extend all the way to the right side of the window. – Arush Agarampur Aug 16 '19 at 01:08
  • You can't change the title bar color unless you manually draw the caption buttons yourself (there are no APIs last time I checked). You could comment out `BufferedPaintSetAlpha` but this is unreliable because the caption could be any color, it also changes color for active/inactive state, so it can hide the caption buttons. You would use this method only if you painting a part of title-bar. You can also omit `FillRect` and use `DrawThemeText`, that enables paiting text. – Barmak Shemirani Aug 16 '19 at 03:31