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

问题描述 投票:0回答:5

我正在开发一个应用程序,它以网格样式在屏幕上定位窗口。在 Windows 10 上运行此程序时,窗口之间存在巨大间隙。进一步的调查表明

GetWindowRect
返回意外的值,包括不可见的边框,但我无法让它返回具有可见边框的真实值。

1)此线程表明这是设计使然,您可以通过与 winver=6 链接来“修复”它。我的环境不允许这样做,但我尝试将 PE

MajorOperatingSystemVersion
MajorSubsystemVersion
更改为 6,没有影响

2)同一线程还建议使用

DwmGetWindowAttribute
DWMWA_EXTENDED_FRAME_BOUNDS
从 DWM 获取真实坐标,这可以工作,但意味着要更改获取窗口坐标的所有位置。它也不允许设置该值,因此我们需要逆向该过程才能设置窗口大小。

3) 这个问题说明在这个过程中缺乏DPI意识。在清单中设置 DPI 感知标志或调用

SetProcessDpiAwareness
都没有任何结果。

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

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

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

winapi vb6 windows-10 dwm
5个回答
42
投票

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`

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


4
投票

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

您已经在使用真实坐标。 Windows10 只是选择隐藏边框,让你看不到。但尽管如此,它们仍然存在。将鼠标移过窗口边缘,光标将更改为调整大小光标,这意味着它实际上仍在窗口上方。

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

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


0
投票

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

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

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

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 感知功能

0
投票

我为那些需要逻辑坐标(例如 GetWindowRect 如何返回但具有正确值(不包括不可见边框))的人发现了一个新的解决方案。 将

DwmGetWindowAttribute
DWMWA_EXTENDED_FRAME_BOUNDS
一起使用将给出正确的值,但在非逻辑坐标中,这在某些情况下会成为问题(例如具有不同 DPI 配置的多个显示器)

该解决方案基于我在 C++ 中的最新发现 - 使用 Win32 API 获取真实屏幕分辨率 - 代码日志

这是该方法的代码,它将为您提供正确的值,而逻辑坐标中没有不可见的边框:

bool GetWindowRectNoInvisibleBorders(HWND hWnd, RECT* rect) { RECT dwmRect; HRESULT hresult = DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, &dwmRect, sizeof(RECT)); if (hresult != S_OK) return false; HMONITOR monitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST); MONITORINFOEX monInfo; monInfo.cbSize = sizeof(MONITORINFOEX); GetMonitorInfo(monitor, &monInfo); DEVMODE monDeviceConfig; monDeviceConfig.dmSize = sizeof(DEVMODE); EnumDisplaySettings(monInfo.szDevice, ENUM_CURRENT_SETTINGS, &monDeviceConfig); auto scalingRatio = (monInfo.rcMonitor.right - monInfo.rcMonitor.left) / (double)monDeviceConfig.dmPelsWidth; rect->left = (dwmRect.left - monDeviceConfig.dmPosition.x) * scalingRatio + monInfo.rcMonitor.left; rect->right = (dwmRect.right - monDeviceConfig.dmPosition.x) * scalingRatio + monInfo.rcMonitor.left; rect->top = (dwmRect.top - monDeviceConfig.dmPosition.y) * scalingRatio + monInfo.rcMonitor.top; rect->bottom = (dwmRect.bottom - monDeviceConfig.dmPosition.y) * scalingRatio + monInfo.rcMonitor.top; return true; }
很难弄清楚。我花了好几年的时间。
这就像拥有 

GetWindowRect

 函数,但没有返回一个与不可见边框有偏移的矩形的问题。


-1
投票
您可以回复

WM_NCCALCSIZE

 消息,修改 
WndProc
 的默认行为以删除不可见边框。

正如

this documentthis 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 }
    
© www.soinside.com 2019 - 2024. All rights reserved.