WM_NCHITTEST返回命中测试结果不准确,调用默认窗口过程前是否需要调整lParam?
WM_NCHITTEST返回命中测试结果不准确,调用默认窗口过程前是否需要调整lParam?
最近在调试WM_NCHITTEST的逻辑时遇到了一个头疼的问题:明明传入的光标坐标看起来是正确的,但手动发送WM_NCHITTEST消息返回的命中测试值总是有偏差。想请教各位,在把lParam传给默认窗口过程之前,是不是需要做什么特殊的坐标调整?
先给大家看一下我用来复现问题的代码:
#include <windows.h> #include <cstdio> #include <functional> int main(int, char**) { WNDCLASSEX wc = { 0 }; wc.cbSize = sizeof(WNDCLASSEX); wc.lpszClassName = TEXT("MRE"); wc.lpfnWndProc = [](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -> LRESULT { static LRESULT ht = HTNOWHERE; switch (msg) { case WM_DESTROY: PostQuitMessage(0); return (LRESULT)0; case WM_NCLBUTTONDOWN: if ((wParam == HTCLOSE) || (wParam == HTMINBUTTON)) { std::printf("\nstart ht: %d\n", wParam); SetCapture(hwnd); ht = wParam; return (LRESULT)0; } break; case WM_NCLBUTTONUP: case WM_LBUTTONUP: if (ht == HTNOWHERE) break; ReleaseCapture(); ht = HTNOWHERE; RECT rect = { }; GetWindowRect(hwnd, &rect); std::printf("window (%d x %d) at (%d, %d)\n", rect.right - rect.left, rect.bottom - rect.top, rect.left, rect.top ); POINT p = { }; bool bSuccess = GetCursorPos(&p); std::printf("%d at (%d, %d)\n", bSuccess, p.x, p.y); std::printf("end ht: %d\n", SendMessage(hwnd, WM_NCHITTEST, 0, MAKELPARAM(p.x, p.y))); break; } return DefWindowProc(hwnd, msg, wParam, lParam); }; if (!RegisterClassEx(&wc)) return 1; const LONG styles = (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX); RECT rectDimensions = { 0u, 0u, 640u, 480u }; AdjustWindowRect(&rectDimensions, styles, false); HWND hwnd = CreateWindow( TEXT("MRE"), TEXT("mre"), styles, 0, 0, (rectDimensions.right - rectDimensions.left), (rectDimensions.bottom - rectDimensions.top), nullptr, nullptr, GetModuleHandle(NULL), nullptr ); MSG msg = { 0 }; ShowWindow(hwnd, SW_NORMAL); while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; }
我运行这段代码后得到了以下测试结果:
注:
start ht是默认窗口过程在WM_NCLBUTTONDOWN时返回的正确命中目标;end ht是我手动发送WM_NCHITTEST后得到的不准确结果;另外(2)对应HTCAPTION,(9)对应HTMAXBUTTON
测试场景1:左键按下最小化按钮(HTMINBUTTON=8),在屏幕左上角抬起左键
start ht: 8 window (656 x 519) at (1042, 297) 1 at (0, 0) end ht: 0
测试场景2:抬起左键时期望命中HTMINBUTTON(8)
理论上HTMINBUTTON应该在窗口原点右侧530px到576px的范围内,我测试的几次坐标都在这个区间里,但返回结果却不稳定:
start ht: 8 window (656 x 519) at (1042, 297) 1 at (1556, 315) end ht: 2 start ht: 8 window (656 x 519) at (1042, 297) 1 at (1570, 308) end ht: 2 start ht: 8 window (656 x 519) at (1042, 297) 1 at (1594, 312) end ht: 8
测试场景3:抬起左键时期望命中HTCLOSE(20)
同样出现了返回结果不符合预期的情况:
start ht: 20 window (656 x 519) at (1042, 297) 1 at (1646, 307) end ht: 9 start ht: 20 window (656 x 519) at (1042, 297) 1 at (1664, 311) end ht: 20 start ht: 20 window (656 x 519) at (1042, 297) 1 at (1683, 311) end ht: 20
我确认了GetCursorPos()返回的屏幕坐标是正确的,而且我理解WM_NCHITTEST接收的就是屏幕坐标,所以直接用这个结果应该没问题。为了解决这个问题我已经尝试了几种方法,但都没有效果:
- 在程序开头调用
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)开启Per-Monitor V2的DPI感知(因为用应用清单的方式不可行) - 使用
PhysicalToLogicalPointForPerMonitorDPI()转换光标坐标后再传入WM_NCHITTEST:POINT p = { }; bool bResult = ((GetCursorPos(&p)) && (PhysicalToLogicalPointForPerMonitorDPI(hwnd, &p))); std::printf("%d at (%d, %d)\n", bResult, p.x, p.y); std::printf("end ht: %d\n", SendMessage(hwnd, WM_NCHITTEST, 0, MAKELPARAM(p.x, p.y))); - 直接使用WM_NCLBUTTONUP消息的lParam作为坐标:
修改了窗口过程中WM_NCLBUTTONUP的处理逻辑,直接从lParam提取坐标,结果和GetCursorPos()返回的完全一致,但问题依旧存在。
现在实在是搞不懂问题出在哪了,有没有大佬能帮忙分析下?是不是我对WM_NCHITTEST的坐标要求理解错了,或者确实需要对lParam做什么特殊的调整?
内容来源于stack exchange
相关产品推荐
相关产品推荐

