Невозможно рисовать на пользовательской оконной раме с DWM

Я создал индивидуальную оконную раму с помощью DWM. Рамка успешно расширяется, но всякий раз, когда я пытаюсь нарисовать ее, расширенная рамка закрывает все, что я пытаюсь нарисовать. Я видел, как другие люди пытались ввести верхний левый угол в отрицательных границах, но даже когда я пытаюсь это сделать, строка заголовка все равно перекрывает рисунок в главном окне. Вот мой код (примечание: у меня нет кода для проверки попадания):

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

person Arush Agarampur    schedule 10.08.2019    source источник
comment
В вашем коде много неизвестного. Пожалуйста, покажите минимальный воспроизводимый пример. Можно ли изменять размер вашего окна? пример WS_OVERLAPPEDWINDOW флаг ...   -  person Barmak Shemirani    schedule 11.08.2019
comment
Да, я изменил его, чтобы было лучше понять.   -  person Arush Agarampur    schedule 11.08.2019


Ответы (1)


Класс окна не имеет курсора по умолчанию, он будет показывать неправильные курсоры при перемещении мыши. Измените wc на

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 также следует обрабатывать, иначе строка заголовка не будет захватываться. Лучше рассчитать толщину границы на основе стиля Windows или оставить ее равной static, потому что она будет необходима на протяжении всей процедуры, как и высота строки заголовка.

Обратите внимание, что этот код будет сильно отличаться в Windows 10 от окна 7, которое имеет странную прозрачную строку заголовка, вам понадобится 32-битное растровое изображение с альфа-каналом для рисования в строке заголовка. Или используйте буферную краску с BufferedPaintSetAlpha, как показано ниже.

#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);
}
person Barmak Shemirani    schedule 11.08.2019
comment
Я вижу, что вы сделали, но прямоугольник все еще не отображается, кажется, что он закрыт оконной рамой. - person Arush Agarampur; 11.08.2019
comment
См. Пример с забуференной краской для окрашивания непрозрачного цвета. - person Barmak Shemirani; 11.08.2019
comment
Неважно, протестируйте на Windows 7, и прямоугольник показывает и работает. Спасибо. - person Arush Agarampur; 11.08.2019
comment
Привет, у меня есть один вопрос - хочу ли я изменить цвет кнопок с заголовками, как в проводнике Windows на W10, когда включена темная тема? Потому что в настоящее время буферизованная краска будет покрывать кнопки с заголовками, когда расширяется до правой стороны окна. - person Arush Agarampur; 16.08.2019
comment
Вы не можете изменить цвет строки заголовка, если вы сами вручную не нарисуете кнопки с заголовками (в прошлый раз, когда я проверял, API-интерфейсы отсутствуют). Вы можете закомментировать BufferedPaintSetAlpha, но это ненадежно, потому что заголовок может быть любого цвета, он также меняет цвет для активного / неактивного состояния, поэтому он может скрыть кнопки заголовков. Вы можете использовать этот метод, только если закрашиваете часть строки заголовка. Вы также можете опустить FillRect и использовать DrawThemeText, что позволяет рисовать текст. - person Barmak Shemirani; 16.08.2019