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!
#define UNICODE
#define _UNICODE
#define STRICT
#define STRICT_TYPED_ITEMIDS
#define WINVER 0x0600
#define _WIN32_WINNT 0x0600
#define _WIN32_WINDOWS 0x0600
#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;
void getMetrics(HWND hwnd, struct metrics *m)
{
RECT r;
GetWindowRect(hwnd, &(m->windowRect));
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;
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;
}
m->realNonclientInsets = m->nonclientInsets;
m->realNonclientInsets.cyTopHeight *= 2.5;
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;
m->relativeClientRect = m->effectiveClientRect;
MapWindowRect(NULL, hwnd, &(m->relativeClientRect));
#if 0
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");
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);
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;
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;
}
break;
case WM_SIZE:
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;
}
pncsp->rgrc[0]
to itself without changing it, then instructsWndProc()
to return 0 without callingDefWIndowProc()
. Unless there's something subtle I'm missing (settingpncsp->rgrc[0]
to itself is not the same as doing nothing?)? – andlabsDwmDefWindowProc()
is not doing the hit testing for some reason, but the page I linked tells me how to do it in this case, which is not a problem.) – andlabs