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 MajorOperatingSystemVersion
和 MajorSubsystemVersion
更改为 6 而没有影响
2) 同一线程还建议使用 DwmGetWindowAttribute
和 DWMWA_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
相同的结果。
哦,我没想到。我只是要删除这个答案。我注意到它的其他问题。请注意SetWindowPos
和GetWindowRect
工作正常。您要求提供边界,系统正在给您提供边界。唯一的问题是边框在 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 返回一个包含“不可见”边框的大小的主要内容,如果未能解决你的问题,请参考以下文章
WinApi中的GetClientRect和GetWindowRect有啥区别?