Почему мое окно DwmExtendFrameIntoClientArea () 'd не рисует границы DWM?

Я следил за руководством MSDN использовать DWM API для расширения фрейма в клиентскую область, но переделать, чтобы я мог поэкспериментировать с ним, когда у меня что-то заработает.

Однако, когда я создаю его, используя cl wincompositiontest.cpp в командной строке встроенных инструментов Visual Studio 2013 x64, программа не рисует границы окна. Стандартные кнопки окна работают: я вижу свечение кнопок и всплывающие подсказки Windows 7, а нажатие на кнопки выполняет соответствующие действия (так что я могу закрыть это окно). Но больше ничего не работает: я не могу перемещать или изменять размер окна с краями, а границы не рисуются, а рисуются белым:

Window

DwmExtendFrameIntoClientArea() возвращает S_OK.

Что происходит? Это Windows 7 x64, работающая в VirtualBox; Я дал код другу, также работающему под управлением Windows 7 на реальном оборудовании, и они получили те же результаты. Спасибо.

// 12 december 2016
#define UNICODE
#define _UNICODE
#define STRICT
#define STRICT_TYPED_ITEMIDS
// get Windows version right; right now Windows Vista
// unless otherwise stated, all values from Microsoft's sdkddkver.h
// TODO is all of this necessary? how is NTDDI_VERSION used?
// TODO plaform update sp2
#define WINVER          0x0600  /* from Microsoft's winnls.h */
#define _WIN32_WINNT        0x0600
#define _WIN32_WINDOWS  0x0600  /* from Microsoft's pdh.h */
#define _WIN32_IE           0x0700
#define NTDDI_VERSION       0x06000000
#include <windows.h>
#include <commctrl.h>
#include <uxtheme.h>
#include <windowsx.h>
#include <shobjidl.h>
#include <d2d1.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <usp10.h>
#include <msctf.h>
#include <textstor.h>
#include <olectl.h>
#include <shlwapi.h>
#include <dwmapi.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <wchar.h>
#include <stdarg.h>
#include <stdio.h>
#include <math.h>
#include <float.h>
#include <inttypes.h>
#include <vector>
#include <map>
#include <string>

#pragma comment(linker, \
    "\"/manifestdependency:type='Win32' " \
    "name='Microsoft.Windows.Common-Controls' " \
    "version='6.0.0.0' " \
    "processorArchitecture='*' " \
    "publicKeyToken='6595b64144ccf1df' " \
    "language='*'\"")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "kernel32.lib")
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "uxtheme.lib")
#pragma comment(lib, "dwmapi.lib")

#define HR(call) printf("%s -> 0x%I32X\n", #call, call)

LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    RECT r;
    MARGINS margins;
    BOOL dwmHandled;
    LRESULT lResult;

    dwmHandled = DwmDefWindowProc(hwnd, uMsg, wParam, lParam, &lResult);
    switch (uMsg) {
    case WM_CREATE:
        GetWindowRect(hwnd, &r);
        SetWindowPos(hwnd, NULL,
            r.left, r.top,
            r.right - r.left, r.bottom - r.top,
            SWP_FRAMECHANGED);
        // TODO if we pass SWP_NOOWNERZORDER || SWP_NOZORDER, the default frame is not correctly inhibited
        break;
    case WM_ACTIVATE:
        margins.cxLeftWidth = 8;
        margins.cxRightWidth = 8;
        margins.cyBottomHeight = 20;
        margins.cyTopHeight = 27;
        HR(DwmExtendFrameIntoClientArea(hwnd, &margins));
        break;
    case WM_NCCALCSIZE:
        if (wParam != (WPARAM) FALSE)
            return 0;
        break;
    case WM_CLOSE:
        PostQuitMessage(0);
        break;
    }
    if (dwmHandled)
        return lResult;
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}

int main(void)
{
    WNDCLASSW wc;
    HWND mainwin;
    MSG msg;

    ZeroMemory(&wc, sizeof (WNDCLASSW));
    wc.lpszClassName = L"mainwin";
    wc.lpfnWndProc = wndproc;
    wc.hInstance = GetModuleHandle(NULL);
    wc.hIcon = LoadIconW(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
    RegisterClassW(&wc);

    mainwin = CreateWindowExW(0,
        L"mainwin", L"Main Window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        400, 400,
        NULL, NULL, GetModuleHandle(NULL), NULL);

    ShowWindow(mainwin, SW_SHOWDEFAULT);
    UpdateWindow(mainwin);

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

person andlabs    schedule 12.12.2016    source источник
comment
Для WM_NCCALCSIZE MSDN: Когда wParam имеет значение TRUE, простой возврат 0 без обработки прямоугольников NCCALCSIZE_PARAMS приведет к изменению размера клиентской области до размера окна, включая рамку окна. Это удалит рамку окна и элементы заголовка из вашего окна, оставив отображаемую только клиентскую область.   -  person γηράσκω δ' αεί πο    schedule 12.12.2016
comment
@ γηράσκωδ'αείπολλάδιδασκόμε да, но взгляните на страницу MSDN, на которую я указал: она просто устанавливает pncsp->rgrc[0] себе, не меняя его, а затем инструктирует WndProc() вернуть 0 без вызова DefWIndowProc(). Если я не упускаю чего-то тонкого (установка pncsp->rgrc[0] на себя - это не то же самое, что ничего не делать?)?   -  person andlabs    schedule 12.12.2016
comment
Измените цвет hbrBackground на черный   -  person γηράσκω δ' αεί πο    schedule 12.12.2016
comment
@ γηράσκωδ'αείπολλάδιδασκόμε хорошо, это исправило все, кроме перетаскивания границы, и теперь я не уверен, почему. Вы знаете? (Для проверки попадания кажется, что DwmDefWindowProc() по какой-то причине не выполняет проверку попадания, но страница, на которую я указал, говорит мне, как это сделать в этом случае, что не является проблемой.)   -  person andlabs    schedule 12.12.2016
comment
Если вы не собираетесь рисовать в строке заголовка, вам не нужен весь дополнительный код. Просто вызовите DwmExtendFrameIntoClientArea, и все будет хорошо   -  person γηράσκω δ' αεί πο    schedule 12.12.2016
comment
@ γηράσκωδ'αείπολλάδιδασκόμε Я еще не рисую, но буду сейчас, когда у меня столько работы. Есть разница?   -  person andlabs    schedule 12.12.2016
comment
Если вы собираетесь рисовать в расширенном фрейме, возможно, вам понадобится код в указанном вами URL. Хотя не уверен.   -  person γηράσκω δ' αεί πο    schedule 12.12.2016
comment
Правильно. Я имел в виду, почему ваше предложение использовать черную кисть для фона исправило мой рисунок окна? Вы это знаете, или это связано с тем, что вы не рисовали?   -  person andlabs    schedule 12.12.2016
comment
Я использовал черную кисть несколько лет назад, когда хотел рисовать прозрачно с помощью DirectX. Идея та же. Растяните рамку на всю форму и установите кисть черного цвета. Я считаю, что установка кисти на черный также устанавливает альфа-значение расширенной области на 0, и, таким образом, вы можете видеть прозрачные границы.   -  person γηράσκω δ' αεί πο    schedule 12.12.2016
comment
Хорошо, это имеет смысл в сочетании с некоторой информацией на этой странице. Теперь я могу продолжить эксперименты с DWM-фреймами. Еще раз спасибо!   -  person andlabs    schedule 12.12.2016
comment
Теперь единственное, что меня интересует, - почему пример MSDN работает с COLOR_WINDOW; может это переход с Vista на 7? ...   -  person andlabs    schedule 12.12.2016
comment
Вы имеете в виду, что пример работал в Vista с COLOR_WINDOW, а не с 7?   -  person γηράσκω δ' αεί πο    schedule 13.12.2016
comment
Нет, этот образец работает в системах Microsoft, поскольку у них есть изображения, чтобы показать его, но в моей системе я получаю результат, который я показал выше, за исключением всего белого. На странице объясняется весь альфа-материал, о котором вы упоминали, но только для тонкой кромки вокруг клиентской области, а не для всего окна, поэтому мне интересно, изменилось ли что-то между Vista и 7.   -  person andlabs    schedule 13.12.2016
comment
Для совместимости с Windows 10 не следует менять левую / правую / нижнюю границу. Вы можете изменить область заголовка и позволить системе обрабатывать другие границы. Или используйте настраиваемые границы, но не добавляйте системные кнопки.   -  person Barmak Shemirani    schedule 13.12.2016
comment
Да, в самом деле. Вы пытались скопировать весь код в точности, как в примере, чтобы проверить, работает ли он? Может ты что-то упустил. Например, в примере дважды вызывается WM_PAINT с некоторым пользовательским рисунком.   -  person γηράσκω δ' αεί πο    schedule 13.12.2016
comment
@BarmakShemirani да? будет ли это делать передача 0 в одном из полей MARGINS? Я только что использовал AdjustWindowRectEx(), чтобы получить системные поля, и я еще не тестировал на Windows 8 или 10 ...   -  person andlabs    schedule 13.12.2016
comment
@ γηράσκωδ'αείπολλάδιδασκόμε по общему признанию нет, но я могу сделать это позже ...   -  person andlabs    schedule 13.12.2016


Ответы (2)


Для совместимости с Windows 10 левое / правое / нижнее поля должны быть нулевыми.

Обычно вам нужно изменить только область заголовка. Поэтому меняйте значение только для margins.cyTopHeight

Вычислите ширину границ по умолчанию для данного стиля окна, используя AdjustWindowRectEx, и передайте эти значения в WM_NCCALCSIZE, это покажет границы по умолчанию.

DwmDefWindowProc требуется только в ответ на WM_NCHITTEST и WM_NCMOUSELEAVE (но, возможно, способ, которым вы его настроили, лучше для будущей совместимости)

Вы также должны обработать WM_PAINT, чтобы системные кнопки отображались правильно. Вероятно, проще скрыть системные кнопки и нарисовать свои собственные, поэтому строка заголовка полностью настраивается с собственным цветом фона.

Ниже приведен пример для Windows 10, он должен работать для Windows 7. Приложение должно поддерживать разрешение на дюйм, иначе возникнут небольшие проблемы с отображением границ.

void paint_caption(HWND hWnd, HDC hdc, int caption_height)
{
    RECT rc;
    GetClientRect(hWnd, &rc);
    rc.bottom = caption_height;

    HDC memdc = CreateCompatibleDC(hdc);
    BITMAPINFOHEADER bmpInfoHdr = 
        { sizeof(BITMAPINFOHEADER), rc.right, -rc.bottom, 1, 32 };
    HBITMAP hbitmap = 
        CreateDIBSection(memdc, (BITMAPINFO*)(&bmpInfoHdr), DIB_RGB_COLORS, 0, 0, 0);
    HGDIOBJ oldbitmap = SelectObject(memdc, hbitmap);

    //Note, GDI functions don't support alpha channel, they can't be used here
    //Use GDI+, BufferedPaint, or DrawThemeXXX functions

    BitBlt(hdc, 0, 0, rc.right, caption_height, memdc, 0, 0, SRCCOPY);
    SelectObject(memdc, oldbitmap);
    DeleteObject(hbitmap);
    DeleteDC(memdc);
}

LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    //static MARGINS margins = { -1,-1,100,-1 };
    static MARGINS margins = { 0,0,100,0 };
    static RECT border_thickness = { 0 };

    switch(uMsg) 
    {
    case WM_CREATE:
        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 };
        }

        DwmExtendFrameIntoClientArea(hwnd, &margins);
        SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
            SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);

        break;

    case WM_NCCALCSIZE:
        if(lParam)
        {
            NCCALCSIZE_PARAMS* sz = (NCCALCSIZE_PARAMS*)lParam;
            sz->rgrc[0].left += border_thickness.left;
            sz->rgrc[0].right -= border_thickness.right;
            sz->rgrc[0].bottom -= border_thickness.bottom;
            return 0;
        }
        break;

    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);

        //paint caption area
        paint_caption(hwnd, hdc, margins.cyTopHeight);

        EndPaint(hwnd, &ps);
        return 0;
    }

    case WM_NCHITTEST:
    {
        //handle close/minimize/maximize/help button
        LRESULT lResult;
        if (DwmDefWindowProc(hwnd, uMsg, wParam, lParam, &lResult))
            return lResult;

        //do default processing, except change the result for caption area
        lResult = DefWindowProc(hwnd, uMsg, wParam, lParam);
        if(lResult == HTCLIENT)
        {
            POINT pt = { LOWORD(lParam), HIWORD(lParam) };
            ScreenToClient(hwnd, &pt);
            if(pt.y < border_thickness.top) return HTTOP;
            if(pt.y < margins.cyTopHeight)  return HTCAPTION;
        }

        return lResult;
    }

    case WM_NCMOUSELEAVE:
    {
        LRESULT lResult;
        if(DwmDefWindowProc(hwnd, uMsg, wParam, lParam, &lResult))
            return lResult;
        break;
    }

    case WM_CLOSE:
        PostQuitMessage(0);
        break;
    }

    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}

Результат в Windows 10:

введите описание изображения здесь

Победа 7:

введите описание изображения здесь

person Barmak Shemirani    schedule 12.12.2016
comment
В документации для DwmDefWindowProc() говорится, что он также должен вызываться для WM_NCMOUSELEAVE, предположительно потому, что это то сообщение, от которого исчезает аура свечения в Windows 7. Что отличает передачу 0 от результата передачи AdjustWindowRectEx() в нулевом прямоугольнике, что приведет к необходимым вставкам? А что в Windows 10 заставляет рисовать некорректно? Я сейчас загрузлюсь в Windows 10, чтобы протестировать код, я должен посмотреть, что произойдет, но, поскольку я не использую его регулярно, я могу что-то упустить ... - person andlabs; 13.12.2016
comment
Я хотел написать WM_NCMOUSELEAVE, а не WM_NCMOUSMOVE. Исправил. Вы можете произвести расчет для AdjustWindowRectEx, как хотите. Вы можете ввести ноль, затем AdjustWindowRectEx вернет небольшие отрицательные значения, а затем отмените эти значения, чтобы найти толщину границы. - person Barmak Shemirani; 13.12.2016
comment
Ах хорошо; Я понимаю, что вы имеете в виду, говоря, что границы должны быть установлены специально в Windows 10; AdjustWindowRectEx() по-прежнему возвращает значения Windows 7/8. Но подождите, разве это не параметр совместимости, который можно переопределить с помощью манифеста? Я, кажется, припоминаю, что это уже обсуждалось здесь в прошлом. Однако я до сих пор не знаю, что делает кнопки с субтитрами неправильными. (Также FWIW все, что я делаю в опубликованном мною коде, взято со страницы MSDN, на которую я указал, включая вызов DwmDefWindowProc() в верхней части wndproc(). К настоящему времени я расширил приведенный выше код. Тем не менее, я не знаю, почему черная кисть .) - person andlabs; 13.12.2016
comment
Код, который я опубликовал, работает в Windows 7. Вы добавляли WM_PAINT case? - person Barmak Shemirani; 13.12.2016
comment
В Windows 10 для изменения размера границ AdjustWindowRectEx должен возвращать 8 пикселей для всех границ, с флагами совместимости или без них. То же, что и Win7 / 8. Возможно, вы думаете о DwmGetWindowAttribute, который дает в Win10 своеобразные прозрачные границы {7,0,7,7} (слева, сверху, справа, снизу) (но здесь это не имеет значения). Да, вы можете поместить DwmDefWindowProc поверх кода, как вы это делали раньше. Вам не нужна черная кисть с приведенным выше кодом. - person Barmak Shemirani; 13.12.2016
comment
Хм, тогда я не совсем понимаю, что вспоминаю. Интересно, если нет способа получить фактическую ширину границы в Windows 10, включены ли тени в границу изменения размера? То есть это все равно действительно 8 пикселей, а не 8 пикселей голубой каймы? В любом случае я предполагаю, что DefWindowProc(), как вы его называете в своем WM_NCHITTEST обработчике, правильно обрабатывает MARGINS==0 случай, верно? Жалко, что делать то, что нужно, больше не работает ... - person andlabs; 13.12.2016
comment
Хм, похоже, что тень, являющаяся частью этих 8 пикселей, похоже, имеет место: окно без DWM-композиции позволяет мне немного изменять размер от края окна, в области тени, и останавливается прямо на синей границе . (Я не уверен, что это даже немного раньше синей границы или нет; я также не уверен, что это ровно 8 пикселей пространства для изменения размера.) Мой код DWM начинает изменять размер и перестает изменять размер на синей границе, черт возьми, тень . Интересный... - person andlabs; 13.12.2016
comment
Кроме того, я до сих пор не могу отличить кнопки окна в Windows 10 в окне, отличном от DWM, и в окне DWM ... В любом случае, если вам интересно, вот текущая версия моего кода с вашим исправления все еще не применены: github.com/andlabs/misctestprogs/blob/master/ Как видите, моя цель - больше протестировать Composited подклассы тем, поскольку они, кажется, являются тем, что элементы управления в расширенном фрейме используют, чтобы увидеть, какие типы пользовательского интерфейса я могу получить. Он только что запущен и может быть разделен на собственное репо ... (вы также заметите, что он еще не совсем идеален; см. WM_SIZE) - person andlabs; 13.12.2016
comment
@BarmakShemirani Вы говорите, что, вероятно, проще скрыть системные кнопки и нарисовать свои собственные, поэтому строка заголовка полностью настраивается с собственным цветом фона .. Я хотел бы знать, как скрыть системные кнопки, например, кнопку закрытия ? Единственный известный мне способ сделать это - отключить стиль WS_SYSMENU при передаче аргумента стиля в CreateWindowEx (). Но это приведет к полному отключению изменения размеров в приведенном выше коде. - person Anurag S Sharma; 27.05.2020

Хорошо, поэтому я исследовал немного больше, пытаясь объединить предложения γηράσκω δ 'αεί πολλά διδασκόμε и Бармака Шемирани с тем, что у меня было изначально (которое было основано на MSDN), чтобы придумать что-то, что, кажется, будет обрабатывать все дела в будущем - доказательный способ. Исчерпывающее тестирование показывает, что приведенный ниже код отлично обрабатывает странное поведение границы Windows 10 при наведении курсора мыши (даже верхняя граница, которая даже в обычных окнах срабатывает только по синему краю, а не немного отличается от других). Это выглядит правильно в Windows 10, Windows 8.1 и Windows 7. Более того, максимизация теперь тоже работает правильно (или, кажется, работает правильно; я не уверен, есть ли тонкая разница, которую мне не хватает или нет)!

Самым большим отличием от кода Бармака является то, что я извлекаю WM_NCCALCSIZE результаты из DefWindowProc() и просто отфильтровываю верхний результат, позволяя мне контролировать верхний край и позволять Windows решать, насколько большими должны быть остальные. Это также означает, что мне не нужно отслеживать border_thickness, как это делает Бармак. Кроме того, он убирает ошибку, которую я заметил с WM_PAINT, и уменьшая размер окна, перекрывая границы, но я не знаю, почему и как ...

Переменная defWindowProcFirst определяет, какое поведение используется. Если вы установите его на FALSE, вы получите поведение до Barmak, которое имело несоответствия Windows 10.

Следует отметить еще несколько моментов:

  • Приведенное ниже еще не обрабатывает WM_PRINTCLIENT.
  • Приведенное ниже не возвращает HTCLIENT для фактической клиентской области; это не должно быть слишком сложно исправить ...
  • Значение, возвращаемое DefWindowProcW() из WM_NCCALCSIZE, не используется, что означает, что rgrc[1], rgrc[2] и lppos никогда не затрагиваются, и мы можем упустить некоторые оптимизации; Мне нужно выяснить, как справиться с этими
  • Еще куча оставшихся TODO

Но все, что было рассмотрено, похоже, работает нормально :) Я должен вернуться и протестировать код MSDN без изменений; Я полагаю, это даст мне результаты, аналогичные defWindowProcFirst = FALSE, хотя ...

А пока спасибо!

// 12 december 2016
#define UNICODE
#define _UNICODE
#define STRICT
#define STRICT_TYPED_ITEMIDS
// get Windows version right; right now Windows Vista
// unless otherwise stated, all values from Microsoft's sdkddkver.h
// TODO is all of this necessary? how is NTDDI_VERSION used?
// TODO plaform update sp2
#define WINVER          0x0600  /* from Microsoft's winnls.h */
#define _WIN32_WINNT        0x0600
#define _WIN32_WINDOWS  0x0600  /* from Microsoft's pdh.h */
#define _WIN32_IE           0x0700
#define NTDDI_VERSION       0x06000000
#include <windows.h>
#include <commctrl.h>
#include <uxtheme.h>
#include <windowsx.h>
#include <shobjidl.h>
#include <d2d1.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <usp10.h>
#include <msctf.h>
#include <textstor.h>
#include <olectl.h>
#include <shlwapi.h>
#include <dwmapi.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <wchar.h>
#include <stdarg.h>
#include <stdio.h>
#include <math.h>
#include <float.h>
#include <inttypes.h>
#include <vector>
#include <map>
#include <string>

#pragma comment(linker, \
    "\"/manifestdependency:type='Win32' " \
    "name='Microsoft.Windows.Common-Controls' " \
    "version='6.0.0.0' " \
    "processorArchitecture='*' " \
    "publicKeyToken='6595b64144ccf1df' " \
    "language='*'\"")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "kernel32.lib")
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "uxtheme.lib")
#pragma comment(lib, "dwmapi.lib")

#define HR(call) printf("%s -> 0x%I32X\n", #call, call)

struct metrics {
    RECT windowRect;
    MARGINS resizeFrameInsets;
    MARGINS nonclientInsets;
    MARGINS realNonclientInsets;
    RECT effectiveClientRect;
    RECT relativeClientRect;
};

BOOL defWindowProcFirst = TRUE;

// TODO this is incorrect when maximized
void getMetrics(HWND hwnd, struct metrics *m)
{
    RECT r;

    GetWindowRect(hwnd, &(m->windowRect));

    // get the margins of the resize frame
    ZeroMemory(&r, sizeof (RECT));
    AdjustWindowRectEx(&r,
        GetWindowStyle(hwnd) & ~WS_CAPTION,
        FALSE,
        GetWindowExStyle(hwnd));
    m->resizeFrameInsets.cxLeftWidth = -r.left;
    m->resizeFrameInsets.cyTopHeight = -r.top;
    m->resizeFrameInsets.cxRightWidth = r.right;
    m->resizeFrameInsets.cyBottomHeight = r.bottom;

    // get non-client insets
    ZeroMemory(&r, sizeof (RECT));
    AdjustWindowRectEx(&r,
        GetWindowStyle(hwnd),
        FALSE,
        GetWindowExStyle(hwnd));
    m->nonclientInsets.cxLeftWidth = -r.left;
    m->nonclientInsets.cyTopHeight = -r.top;
    m->nonclientInsets.cxRightWidth = r.right;
    m->nonclientInsets.cyBottomHeight = r.bottom;
    if (defWindowProcFirst) {
        m->nonclientInsets.cxLeftWidth = 0;
        m->nonclientInsets.cxRightWidth = 0;
        m->nonclientInsets.cyBottomHeight = 0;
    }

    // give the top 2.5x the room so we can shove stuff in there
    m->realNonclientInsets = m->nonclientInsets;
    m->realNonclientInsets.cyTopHeight *= 2.5;

    // compute the effective client rect
    m->effectiveClientRect = m->windowRect;
    m->effectiveClientRect.left += m->realNonclientInsets.cxLeftWidth;
    m->effectiveClientRect.top += m->realNonclientInsets.cyTopHeight;
    m->effectiveClientRect.right -= m->realNonclientInsets.cxRightWidth;
    m->effectiveClientRect.bottom -= m->realNonclientInsets.cyBottomHeight;

    // and compute it relative to the window's real client rect
    m->relativeClientRect = m->effectiveClientRect;
    MapWindowRect(NULL, hwnd, &(m->relativeClientRect));

#if 0
// for debugging
printf("***\n");
#define PRINTRECT(r) ((int)((r).left)), ((int)((r).top)), ((int)((r).right)), ((int)((r).bottom))
printf("window rect %d %d %d %d\n", PRINTRECT(m->windowRect));
    ZeroMemory(&r, sizeof (RECT));
    AdjustWindowRectEx(&r,
        GetWindowStyle(hwnd),
        FALSE,
        GetWindowExStyle(hwnd));
r.left=-r.left;r.top=-r.top;
printf("edge insets %d %d %d %d\n", PRINTRECT(r));
HR(DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &r, sizeof (RECT)));
printf("DWMWA_EXTENDED_FRAME_BOUNDS %d %d %d %d\n", PRINTRECT(r));
{HMODULE m;
m=LoadLibraryW(L"kernel32.dll");
if(m == NULL)printf("unknown os\n");
// TODO this doesn't work; apparently the function is really in one of the api-ms-core* DLLs...
else if(GetProcAddress(m,"VirtualAllocFromApp")!=NULL)printf("windows 10\n");
else if(GetProcAddress(m,"GetPackageApplicationIds")!=NULL)printf("windows 8.1\n");
else if(GetProcAddress(m,"GetSystemTimePreciseAsFileTime")!=NULL)printf("windows 8\n");
else printf("windows 7\n");}
printf("\n");
#endif
}

HWND rebarHost;
HWND rebar;

const char *htnames[] = {
    "HTERROR",
    "HTTRANSPARENT",
    "HTNOWHERE",
    "HTCLIENT",
    "HTCAPTION",
    "HTSYSMENU",
    "HTGROWBOX",
    "HTMENU",
    "HTHSCROLL",
    "HTVSCROLL",
    "HTMINBUTTON",
    "HTMAXBUTTON",
    "HTLEFT",
    "HTRIGHT",
    "HTTOP",
    "HTTOPLEFT",
    "HTTOPRIGHT",
    "HTBOTTOM",
    "HTBOTTOMLEFT",
    "HTBOTTOMRIGHT",
    "HTBORDER",
    "HTOBJECT",
    "HTCLOSE",
    "HTHELP",
};

LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    struct metrics m;
    HDC dc;
    PAINTSTRUCT ps;
    BOOL dwmHandled;
    LRESULT lResult;

    dwmHandled = DwmDefWindowProc(hwnd, uMsg, wParam, lParam, &lResult);
    getMetrics(hwnd, &m);
    switch (uMsg) {
    case WM_CREATE:
        SetWindowPos(hwnd, NULL,
            m.windowRect.left, m.windowRect.top,
            m.windowRect.right - m.windowRect.left, m.windowRect.bottom - m.windowRect.top,
            SWP_FRAMECHANGED);
        // TODO if we pass SWP_NOOWNERZORDER || SWP_NOZORDER, the default frame is not correctly inhibited

        rebarHost = CreateWindowExW(0,
            L"rebarHost", L"",
            WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
            m.realNonclientInsets.cxLeftWidth,
            m.nonclientInsets.cyTopHeight,
            m.windowRect.right - m.windowRect.left -
                m.realNonclientInsets.cxLeftWidth - m.realNonclientInsets.cxRightWidth,
            m.realNonclientInsets.cyTopHeight - m.nonclientInsets.cyTopHeight,
            hwnd, NULL, GetModuleHandle(NULL), NULL);

        rebar = CreateWindowExW(0,
            REBARCLASSNAMEW, L"",
            WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | RBS_VARHEIGHT | CCS_NODIVIDER,
            0, 0, 0, 0,
            rebarHost, NULL, GetModuleHandle(NULL), NULL);

        {
            REBARBANDINFOW rb;

            ZeroMemory(&rb, sizeof (REBARBANDINFOW));
            rb.cbSize = sizeof (REBARBANDINFOW);
            rb.fMask = RBBIM_TEXT;
            rb.lpText = L"This is a rebar";
            HR((HRESULT) SendMessageW(rebar, RB_INSERTBANDW, (WPARAM) (-1), (LPARAM) (&rb)));
        }

        SendMessageW(rebar, RB_SETWINDOWTHEME, 0,
            (LPARAM) L"NavbarComposited");

        break;
    case WM_ACTIVATE:
        HR(DwmExtendFrameIntoClientArea(hwnd, &(m.realNonclientInsets)));
        break;
    case WM_NCCALCSIZE:
        if (wParam != (WPARAM) FALSE) {
            NCCALCSIZE_PARAMS *op = (NCCALCSIZE_PARAMS *) lParam;
            NCCALCSIZE_PARAMS np;

            if (!defWindowProcFirst)
                return 0;
            np = *op;
            DefWindowProcW(hwnd, uMsg, wParam, (LPARAM) (&np));
            printf("old %ld %ld %ld %ld\nnew %ld %ld %ld %ld\n",
                op->rgrc[0].left, op->rgrc[0].top, op->rgrc[0].right, op->rgrc[0].bottom,
                np.rgrc[0].left, np.rgrc[0].top, np.rgrc[0].right, np.rgrc[0].bottom);
            op->rgrc[0].left = np.rgrc[0].left;
            op->rgrc[0].right = np.rgrc[0].right;
            op->rgrc[0].bottom = np.rgrc[0].bottom;
            return 0;
        }
        break;
    case WM_NCHITTEST:
        if (dwmHandled)
            return lResult;
        // DWM did not handle it; we have to do it ourselves
        if (defWindowProcFirst) {
            lResult = DefWindowProcW(hwnd, uMsg, wParam, lParam);
            if (lResult != HTCLIENT) {
                printf("them %s\n", htnames[lResult + 2]);
                return lResult;
            }
        }
        {
            POINT p;

            p.x = GET_X_LPARAM(lParam);
            p.y = GET_Y_LPARAM(lParam);

            lResult = HTNOWHERE;
            if (p.y >= m.windowRect.top && p.y < (m.windowRect.top + m.resizeFrameInsets.cyTopHeight))
                lResult = HTTOP;
            else if (p.y >= m.effectiveClientRect.bottom && p.y < m.windowRect.bottom)
                lResult = HTBOTTOM;

            if (p.x >= m.windowRect.left && p.x < m.effectiveClientRect.left)
                switch (lResult) {
                case HTNOWHERE:
                    lResult = HTLEFT;
                    break;
                case HTTOP:
                    lResult = HTTOPLEFT;
                    break;
                case HTBOTTOM:
                    lResult = HTBOTTOMLEFT;
                    break;
                }
            else if (p.x >= m.effectiveClientRect.right && p.x < m.windowRect.right)
                switch (lResult) {
                case HTNOWHERE:
                    lResult = HTRIGHT;
                    break;
                case HTTOP:
                    lResult = HTTOPRIGHT;
                    break;
                case HTBOTTOM:
                    lResult = HTBOTTOMRIGHT;
                    break;
                }

            if (lResult == HTNOWHERE)
                if (p.y >= (m.windowRect.top + m.resizeFrameInsets.cyTopHeight) && p.y < m.effectiveClientRect.top)
                    lResult = HTCAPTION;

            if (defWindowProcFirst)
                printf("us %s\n", htnames[lResult + 2]);
            if (lResult != HTNOWHERE)
                return lResult;
        }
        // we can't handle it; give it to DefWindowProcW() and hope for the best
        break;
    case WM_SIZE:
        // TODO if defWindowProcFirst == FALSE, this seems to be wrong when shrinking the size on the right or bottom edges
        // TODO without this call, the WM_PAINT never fills new areas
        // we may need to handle WM_WINDOWPOSCHANGED and compute new metrics from there
        // TODO what happens with defWindowProcFirst == TRUE? do we need to do anything else special? is this needed?
        InvalidateRect(hwnd, &(m.relativeClientRect), FALSE);
        break;
    case WM_PAINT:
        dc = BeginPaint(hwnd, &ps);
        FillRect(dc, &(m.relativeClientRect), (HBRUSH) (COLOR_BTNFACE + 1));
        EndPaint(hwnd, &ps);
        break;
    case WM_CLOSE:
        PostQuitMessage(0);
        break;
    }
    if (dwmHandled)
        return lResult;
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}

int main(void)
{
    INITCOMMONCONTROLSEX icc;
    WNDCLASSW wc;
    HWND mainwin;
    MSG msg;

    ZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX));
    icc.dwSize = sizeof (INITCOMMONCONTROLSEX);
    icc.dwICC = (ICC_LISTVIEW_CLASSES | ICC_TREEVIEW_CLASSES | ICC_BAR_CLASSES | ICC_TAB_CLASSES | ICC_UPDOWN_CLASS | ICC_PROGRESS_CLASS | ICC_HOTKEY_CLASS | ICC_ANIMATE_CLASS | ICC_WIN95_CLASSES | ICC_DATE_CLASSES | ICC_USEREX_CLASSES | ICC_COOL_CLASSES | ICC_INTERNET_CLASSES | ICC_PAGESCROLLER_CLASS | ICC_NATIVEFNTCTL_CLASS | ICC_STANDARD_CLASSES | ICC_LINK_CLASS);
    InitCommonControlsEx(&icc);

    ZeroMemory(&wc, sizeof (WNDCLASSW));
    wc.lpszClassName = L"mainwin";
    wc.lpfnWndProc = wndproc;
    wc.hInstance = GetModuleHandle(NULL);
    wc.hIcon = LoadIconW(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
    RegisterClassW(&wc);

    ZeroMemory(&wc, sizeof (WNDCLASSW));
    wc.lpszClassName = L"rebarHost";
    wc.lpfnWndProc = DefWindowProcW;
    wc.hInstance = GetModuleHandle(NULL);
    wc.hIcon = LoadIconW(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
    RegisterClassW(&wc);

    mainwin = CreateWindowExW(0,
        L"mainwin", L"Main Window",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        400, 400,
        NULL, NULL, GetModuleHandle(NULL), NULL);

    ShowWindow(mainwin, SW_SHOWDEFAULT);
    UpdateWindow(mainwin);

    while (GetMessageW(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }
    return 0;
}
person andlabs    schedule 13.12.2016