GetWindowRect 返回一个包含“不可见”边框的大小

Posted

技术标签:

【中文标题】GetWindowRect 返回一个包含“不可见”边框的大小【英文标题】:GetWindowRect returns a size including "invisible" borders 【发布时间】:2016-03-12 09:40:03 【问题描述】:

我正在开发一个以网格样式在屏幕上定位窗口的应用程序。在 Windows 10 上运行此程序时,窗口之间存在巨大差距。进一步调查显示GetWindowRect 正在返回意外的值,包括不可见的边框,但我无法让它返回带有可见边框的真实值。

1) This thread 建议这是设计使然,您可以通过与 winver=6 链接来“修复”它。我的环境不允许这样做,但我尝试将 PE MajorOperatingSystemVersionMajorSubsystemVersion 更改为 6 而没有影响

2) 同一线程还建议使用 DwmGetWindowAttributeDWMWA_EXTENDED_FRAME_BOUNDS 从 DWM 获取真实坐标,这可行,但意味着在获取窗口坐标的任何地方进行更改。它也不允许设置值,让我们反转过程以便能够设置窗口大小。

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

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

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

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

【问题讨论】:

对于最大化窗口或小窗口,你期望得到的坐标和得到的坐标是多少? @barmak 我希望当我将其设置为 0x0 时,窗口位于左上角,而不是它实际显示的 7x0。看截图。 这是 VB6 还是 VB.NET? @IInspectable 我的测试代码是 VB6,如标记,但问题也会影响 Win32 API。 This question 再次表示将子系统更改为 6.0 应该可以工作。我明天必须再次测试,因为我只更改了 PE 标头。 【参考方案1】:

Windows 10 的左、右和底部有细的不可见边框,用于握住鼠标调整大小。边框可能如下所示:7,0,7,7(左、上、右、下)

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

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

您可以欺骗窗口并告诉它转到此处:-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`

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

【讨论】:

这变得更好了......这个方法只在窗口显示后才有效:( 在那之前,DWM 函数只返回与 GetWindowRect 相同的结果。 哦,我没想到。我只是要删除这个答案。我注意到它的其他问题。请注意SetWindowPosGetWindowRect 工作正常。您要求提供边界,系统正在给您提供边界。唯一的问题是边框在 Windows 10 中是不可见的,因此看起来窗口位于错误的位置。 Visual Studio IDE有自己的工具窗口,当工具窗口停靠时,它不使用边框或自定义NC_PAINT;当它的工具窗口浮动时,它使用默认边框。我想你想要类似的东西? 我想让窗口让我使用窗口的真实可见矩形:) 这个答案很有用,所以我希望它不要被删除,因为它是唯一的解决方案到目前为止... 注意DwmGetWindowAttribute()返回物理坐标,但GetWindowRect()返回逻辑坐标。因此,在缩放到 100% 以外的屏幕上的非 DPI 感知应用程序的边框宽度将是错误的。 回应@IanGoldby 的评论 - 这是一个很好的呼喊,但在我看来,如果您对坐标进行系统调用,始终确保您的应用程序始终以 DPI 感知运行是更好的建议。在这种情况下,DWM 和 User32 调用将同意并返回物理坐标。如果不指定 DPI Awareness,有很多 windows API 函数会像这样不一致【参考方案2】:

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

您已经在使用真实坐标。 Windows10 只是选择隐藏您的眼睛的边框。但尽管如此,他们仍然在那里。将鼠标移过窗口边缘,您的光标将变为调整大小的光标,这意味着它实际上仍然在窗口上方。

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

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

【讨论】:

这就是我得出的结论。然而,更改主题不是一种选择,因为这是一个商业应用程序。强制最终用户使用主题通常被认为是不好的做法:-) 我同意更改主题是一个坏主意,而且我不需要我的用户。但他们仍然抱怨差距:(【参考方案3】:

AdjustWindowRectEx(或在 Windows 10 及更高版本上AdjustWindowRectExForDpi)可能有用。这些函数会将客户矩形转换为窗口大小。

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

这是我的代码库中的一个快速 sn-p,我已成功使用这些设置窗口大小以获得所需的客户端大小,请原谅错误处理宏:

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

上述用例中仍有两个歧义:

我的窗口确实有一个菜单,但我必须为菜单参数传入 false,否则我得到错误的大小。如果我弄清楚为什么会这样,我会用解释更新这个答案! 我还没有读到关于 Windows 如何处理 DPI 感知的信息,所以我不确定你什么时候想要使用该功能而不是非 DPI 感知功能

【讨论】:

【参考方案4】:

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

正如this document 和this document 解释的那样,当wParam > 0 时,根据请求wParam.Rgrc[0] 包含窗口的新坐标,当程序返回时,响应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
    

【讨论】:

以上是关于GetWindowRect 返回一个包含“不可见”边框的大小的主要内容,如果未能解决你的问题,请参考以下文章

从另一个片段导航后,ViewPager在返回时不可见

Windows 7 上的 GetWindowRect 太小

为啥他们在 GetWindowRect 函数中使用它

WinApi中的GetClientRect和GetWindowRect有啥区别?

Linux 的 PInvoke GetWindowRect 等效项

从外部窗口的 GetWindowRect 获取 DPI 感知正确的 RECT