andlabs
andlabs

Reputation: 11588

Why is my DwmExtendFrameIntoClientArea()'d window not drawing the DWM borders?

I've been following MSDN's guide to using the DWM API to extend the frame into the client area, but redone so I can experiment with it once I have something working.

However, when I build it, using cl wincompositiontest.cpp in the Visual Studio 2013 x64 Native Tools Command Line, the program does not draw the window borders. The standard window buttons do work: I can see the Windows 7 button glow and tooltips, and clicking on the buttons does their respective action (so I can close this window). But nothing else works: I can't move or resize the window with the edges, and the borders don't draw, instead drawing white:

Window

DwmExtendFrameIntoClientArea() returns S_OK.

What's going on? This is on Windows 7 x64 running in VirtualBox; I gave the code to a friend also running Windows 7 on real hardware and they get the same results. Thanks.

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

Upvotes: 3

Views: 7228

Answers (2)

andlabs
andlabs

Reputation: 11588

All right, so I investigated a bit more, trying to incorporate γηράσκω δ' αεί πολλά διδασκόμε's and Barmak Shemirani's suggestions with what I had initially (which was based on MSDN) to come up with something that seems to handle all cases in a future-proof way. Exhaustive testing shows that the below code handles Windows 10's strange border mouse-over behavior perfectly (even the top border, which even in normal windows only triggers at the blue edge, not slightly off like the others). It looks correct on Windows 10, Windows 8.1, and Windows 7. And what's more, maximizing now works properly too (or seems to work properly; I'm not sure if there's a subtle difference I'm missing or not)!

The biggest difference from Barmak's code is that I pull the WM_NCCALCSIZE results from DefWindowProc() and just filter out the top result, allowing me to take control of the top edge and let Windows decide how big the rest should be. It also means I don't need to keep track of border_thickness like Barmak does. Plus, it cleans up a bug I've noticed with WM_PAINT and sizing the window smaller overlapping into the borders, but I don't know why or how...

The defWindowProcFirst variable is what controls which behavior is used. If you set it to FALSE, you get the pre-Barmak behavior, which had the Windows 10 inconsistencies.

There are a few additional things to note:

  • The below doesn't handle WM_PRINTCLIENT yet.
  • The below doesn't return HTCLIENT for the actual client area; that shouldn't be too hard of a fix...
  • The DefWindowProcW() return value from WM_NCCALCSIZE is not used, which means rgrc[1], rgrc[2], and lppos are never touched and we might be missing out on some optimizations; I'll need to figure out how to handle those
  • A bunch of leftover TODOs as well

But all things considered this seems to work fine :) I should go back and test the MSDN code unmodified though; I do imagine it'll give me similar results to defWindowProcFirst = FALSE though...

Thanks in the meantime!

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

Upvotes: 1

Barmak Shemirani
Barmak Shemirani

Reputation: 31609

For compatibility with Windows 10, the left/right/bottom margins should be zero.

Usually you need to change the title bar area only. Therefore, only change the value for margins.cyTopHeight

Calculate the default border widths for the given window style using AdjustWindowRectEx and pass these values to WM_NCCALCSIZE, this will show the default borders.

DwmDefWindowProc is required in response to WM_NCHITTEST and WM_NCMOUSELEAVE only (but maybe the way you have set it up is better for future compatibility)

You also have to handle WM_PAINT for system buttons to display correctly. It's probably easier to hide the system buttons and draw your own buttons, so the title-bar is completely custom with its own background color.

Below is example for Windows 10, it should work for Windows 7. Application should be DPI aware otherwise there will be small display problems with borders.

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

Result in Windows 10:

enter image description here

Win 7:

enter image description here

Upvotes: 4

Related Questions