我正在开发一个应用程序,它以网格样式在屏幕上定位窗口。在 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 让我使用真实的窗口坐标?
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`
除非有更简单的解决方案!
如何说服 Windows 让我使用真实的窗口坐标?
您已经在使用真实坐标。 Windows10 只是选择隐藏边框,让你看不到。但尽管如此,它们仍然存在。将鼠标移过窗口边缘,光标将更改为调整大小光标,这意味着它实际上仍在窗口上方。
如果您希望您的眼睛与 Windows 告诉您的内容相符,您可以尝试使用 Aero Lite 主题暴露这些边框,以便它们再次可见:
http://winaero.com/blog/enable-the-hidden-aero-lite-theme-in-windows-10/
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
));
上述用例中还有两个不明确的地方:
我为那些需要逻辑坐标(例如 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
函数,但没有返回一个与不可见边框有偏移的矩形的问题。
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
}