You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 06:53:05