GetWindowRect returns 包含 "invisible" 边框的尺寸

GetWindowRect returns a size including "invisible" borders

我正在开发一款以网格样式在屏幕上定位 windows 的应用程序。当运行这对Windows10时,windows之间存在着巨大的差距。进一步的调查显示 GetWindowRect 是 return 意想不到的值,包括不可见的边框,但我无法将其变为 return 具有可见边框的实际值。

1) This thread 表明这是设计使然,您可以通过与 winver=6 链接来 "fix" 它。我的环境不允许这样做,但我已经尝试将 PE MajorOperatingSystemVersionMajorSubsystemVersion 更改为 6 而不影响

2) 同一线程还建议使用 DwmGetWindowAttributeDWMWA_EXTENDED_FRAME_BOUNDS 从 DWM 获取真实坐标,这有效,但意味着在获取 window 坐标的任何地方都需要更改。它还不允许设置值,让我们逆向过程才能设置 window 大小。

3) 表明它在这个过程中缺乏 DPI 意识。在清单中设置 DPI 感知标志或调用 SetProcessDpiAwareness 都没有任何结果。

4) 一时兴起,我还尝试添加 Windows Vista、7、8、8.1 和 10 兼容性标志,Windows 主题清单没有任何变化。

这个 window 被移动到 0x0, 1280x1024,据说会填满整个屏幕,当查询坐标回来时,我们得到相同的值。 然而 window 实际上窄了 14 个像素,以考虑旧版本 Windows 的边框。

如何说服 Windows 让我使用真实的 window 坐标?

Windows 10 左、右、下都有细细的隐形边框,用来夹住鼠标调整大小。边框可能如下所示:7,0,7,7(左、上、右、下)

当您调用 SetWindowPos 将 window 置于此坐标时:
0, 0, 1280, 1024

window 将选择那些精确的坐标,而 GetWindowRect 将 return 选择相同的坐标。但在视觉上,window 似乎在这里:
7, 0, 1273, 1017

你可以欺骗 window 并告诉它去这里:
-7, 0, 1287, 1031

为此,我们得到 Windows 10 边框厚度:

RECT rect, frame;
GetWindowRect(hwnd, &rect);
DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frame, sizeof(RECT));

//rect should be `0, 0, 1280, 1024`
//frame should be `7, 0, 1273, 1017`

RECT border;
border.left = frame.left - rect.left;
border.top = frame.top - rect.top;
border.right = rect.right - frame.right;
border.bottom = rect.bottom - frame.bottom;

//border should be `7, 0, 7, 7`

然后像这样偏移矩形:

rect.left -= border.left;
rect.top -= border.top;
rect.right += border.left + border.right;
rect.bottom += border.top + border.bottom;

//new rect should be `-7, 0, 1287, 1031`

除非有更简单的解决方案!

How can I convince Windows to let me work with the real window coordinates?

您已经在使用真实坐标。 Windows10 只是选择隐藏边框不让您看到。但尽管如此,它们仍然存在。将鼠标悬停在 window 的边缘,您的光标将变为调整大小的光标,这意味着它实际上仍在 window.

上方

如果您希望您的眼睛与 Windows 告诉您的内容相符,您可以尝试使用 Aero Lite 主题暴露这些边框,以便它们再次可见:

http://winaero.com/blog/enable-the-hidden-aero-lite-theme-in-windows-10/

您可以回复WM_NCCALCSIZE消息,修改WndProc的默认行为以移除不可见边框。

正如this document and this document所解释的那样,当wParam > 0时,根据请求wParam.Rgrc[0]包含window的新坐标并且当程序returns时,响应 wParam.Rgrc[0] 包含新客户端矩形的坐标。

golang 代码示例:

case win.WM_NCCALCSIZE:
    log.Println("----------------- WM_NCCALCSIZE:", wParam, lParam)

    if wParam > 0 {
        params := (*win.NCCALCSIZE_PARAMS)(unsafe.Pointer(lParam))
        params.Rgrc[0].Top = params.Rgrc[2].Top
        params.Rgrc[0].Left = params.Rgrc[0].Left + 1
        params.Rgrc[0].Bottom = params.Rgrc[0].Bottom - 1
        params.Rgrc[0].Right = params.Rgrc[0].Right - 1
        return 0x0300
    }

AdjustWindowRectEx (or on Windows 10 and later AdjustWindowRectExForDpi) 可能有用。这些函数会将客户端矩形转换为 window 大小。

我猜你不想重叠边界,所以这可能不是一个完整的解决方案——但它可能是解决方案的一部分,并且可能对遇到这个问题的其他人有用.

这是我的代码库中的一个快速片段,我已经成功地使用它们来设置 window 大小以获得所需的客户端大小,请原谅错误处理宏:

DWORD window_style = (DWORD)GetWindowLong(global_context->window, GWL_STYLE);
CHECK_CODE(window_style);
CHECK(window_style != WS_OVERLAPPED); // Required by AdjustWindowRectEx

DWORD window_style_ex = (DWORD)GetWindowLong(global_context->window, GWL_EXSTYLE);
CHECK_CODE(window_style_ex);

// XXX: Use DPI aware version?
RECT requested_size = {};
requested_size.right = width;
requested_size.bottom = height;
AdjustWindowRectEx(
    &requested_size,
    window_style,
    false, // XXX: Why always false here?
    window_style_ex
);

UINT set_window_pos_flags = SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER;
CHECK_CODE(SetWindowPos(
    global_context->window,
    nullptr,
    0,
    0,
    requested_size.right - requested_size.left,
    requested_size.bottom - requested_size.top,
    set_window_pos_flags
));

上面的用例还有两点不明确:

  • 我的 window 有一个菜单,但我必须为菜单参数传递 false,否则我会得到错误的尺寸。如果我弄清楚这是为什么,我会用解释更新这个答案!
  • 我还没有读到 Windows 如何处理 DPI 感知,所以我不确定你什么时候想使用该功能与非 DPI 感知功能