7

我阅读了 Qt 文档,查看了 SDK 提供的几个示例,我从源代码构建了 Qt Creator 以了解 Qt 开发人员是如何做到的......仍然没有运气。

我正在为 Windows 和 Mac 开发一个跨平台应用程序。在 Mac 方面,我基本上可以尝试我的任何解决方案,它们都可以完美运行(我想这要归功于 MacOS)。另一方面,在 Windows 上,我总是会发现某种错误或粗略的行为。

在我详细介绍之前,我的问题的根源是支持具有不同分辨率的显示器的多个显示器环境。

简而言之,我的两个主要解决方案:

  1. 由于我主要在 QML 中编写应用程序,因此我将ApplicationWindow其用于主窗口。我保存了我的ApplicationWindow状态Settings。我的代码考虑了先前保存的位置是否仍然有效(例如,如果应用程序在监视器上时关闭,不再可用)......我必须这样做的唯一原因是因为 Windows 只会在“外层空间”中打开我的应用程序窗口(Mac 会自动处理)。如果我在其中一个监视器上关闭我的应用程序,然后我更改其他监视器的缩放因子,然后我重新打开我的应用程序,我的应用程序(在 Windows 上)会进入一个非常奇怪的状态。它在正确的显示器上打开,但它的比例太大了,UI 元素只是奇怪地浮动。如果我调整窗口大小,一切都会恢复正常。
  2. 我将 QML 暴露ApplicationWindow给放入 QWidget 容器中的 C++,然后我QMainWindow通过将其设置为setCentralWidget. 使用这种方法,我可以访问saveGeometryand restoreGeometry,它会自动处理多个显示器的定位,但我在 1. 中描述的缩放异常仍然存在。

有人解决了这个问题吗?在此先感谢您的帮助和欣

4

1 回答 1

5

@retif 要求我在几个月前评论我知道这些类型的问题时提供一篇文章。

TLDR

在 Windows 操作系统上处理 Qt Windows 的绝对定位问题时——尤其是在 Windows 10 上,最好使用系统 DPI 感知。当您试图获得最佳缩放时,从 Windows 坐标空间(在不同的 DPI 感知级别)到 Qt 坐标空间时,需要进行一些插值。

这是我在团队申请中所做的。

问题:

当有多个显示器和多个 DPI 分辨率要应对时,很难对 Qt 窗口进行绝对定位。

我们的应用程序窗口从 Windows 任务托盘图标(或 Mac 上的菜单栏图标)“弹出”。

原始代码将获取托盘图标的 Windows 屏幕坐标位置,并将其用作计算窗口位置的参考。

在应用程序启动时,在 Qt 初始化之前,我们将环境变量设置QT_SCALE_FACTOR(systemDPI/96.0). 示例代码:

HDC hdc = GetDC(nullptr);
unsigned int dpi = ::GetDeviceCaps(hdc, LOGPIXELSX);
stringstream dpiScaleFactor;
dpiScaleFactor << (dpi / 96.0);
qputenv("QT_SCALE_FACTOR", QByteArray::fromStdString(dpiScaleFactor.str()));    

上面的代码采用主监视器“DPI 比例”并告诉 Qt 匹配它。它具有让 Qt 本地计算所有缩放而不是像 Windows 在非 DPI 感知应用程序中那样进行位图拉伸的令人愉快的效果。

因为我们使用QT_SCALE_FACTOR环境变量(基于主监视器 DPI)初始化 Qt,所以在转换为 Qt 的 QScreen 坐标空间以进行初始窗口放置时,我们使用该值来缩放 Windows 坐标。

在单显示器场景下一切正常。只要两台显示器上的 DPI 相同,它甚至在多显示器场景下也能正常工作。但是在具有不同 DPI 的多台显示器的配置上,事情就发生了。如果由于屏幕更改或插入(或拔出)投影仪而不得不在非主监视器上弹出窗口,就会发生奇怪的事情。Qt 窗口会出现在错误的位置。或者在某些情况下,窗口内的内容会不正确地缩放。当它确实起作用时,当定位在以不同 DPI 运行的类似大小的显示器上时,会出现缩放到一个 DPI 的窗口“太大”或“太小”的问题。

我最初的调查显示,Qt 的不同 QScreens 几何图形的坐标空间看起来不对劲。每个 QScreen 矩形的坐标是根据 QT_SCALE_FACTOR 缩放的,但是各个 QScreen 矩形的相邻轴没有对齐。例如,一个 QScreen rect 可能是{0,0,2559,1439},但右边的监视器会在{3840,0,4920,1080}。那个地区发生了什么事2560 <= x < 3840?因为我们基于 QT_SCALE_FACTOR 或 DPI 缩放 x 和 y 的代码依赖于位于 (0,0) 的主监视器并且所有监视器都具有相邻的坐标空间。如果我们的代码将假定的位置坐标缩放到另一个监视器上的某个东西,它可能会定位在一个奇怪的地方。

花了一段时间才意识到这本身不是 Qt 错误。只是 Qt 只是在规范化具有这些奇怪坐标空间间隙的 Windows 坐标空间。

修复:

更好的解决方法是告诉 Qt 缩放到主监视器的 DPI 设置,并在系统感知 DPI 模式而不是每监视器感知 DPI 模式下运行进程。这样做的好处是可以让 Qt 正确缩放窗口,并且在主监视器上没有模糊或像素化,并让 Windows 在监视器更改时对其进行缩放。

一点背景。阅读MSDN上高 DPI 编程这一部分的所有内容。很好的阅读。

这就是我们所做的。

QT_SCALE_FACTOR如上所述保持初始化。

然后我们将进程和 Qt 的初始化从每监视器 DPI 感知切换到系统感知 DPI。system-dpi 的好处是它允许 Windows 自动将应用程序窗口缩放到预期的大小,因为监视器从它下面改变。(所有 Windows API 的行为就像所有显示器都具有相同的 DPI)。如上所述,当 DPI 与主显示器不同时,Windows 会在后台进行位图拉伸。因此,在显示器切换上存在一个“模糊问题”需要解决。但它肯定比以前做的更好!

默认情况下,Qt 将尝试将进程初始化为每个监视器感知的应用程序。要强制它以系统 dpi 感知运行,请在 Qt 初始化之前在应用程序启动SetProcessDpiAwareness时使用值非常早的调用。PROCESS_SYSTEM_DPI_AWARE之后 Qt 将无法更改它。

只需切换到系统感知 dpi 即可解决许多其他问题。

最终错误修复:

因为我们将窗口定位在一个绝对位置(任务托盘中系统托盘图标的正上方),所以我们依靠 Windows API Shell_NotifyIconGetRect来为我们提供系统托盘的坐标。一旦我们知道系统托盘的偏移量,我们就会计算一个顶部/左侧位置,以便我们的窗口位于屏幕上。让我们称这个位置X1,Y1

但是,Shell_NotifyIconGetRect在 Windows 10 上返回的坐标将始终是“每个显示器感知”的本机坐标,而不是缩放到系统 DPI。使用PhysicalToLogicalPointForPerMonitorDPI进行转换。此 API 在 Windows 7 上不存在,但不是必需的。如果您支持 Windows 7,请将此 API使用LoadLibraryGetProcAddress。如果该 API 不存在,请跳过此步骤。用于PhysicalToLogicalPointForPerMonitorDPI转换X1,Y1为系统感知的 DPI 坐标,我们将调用X2,Y2.

理想情况下,X2,Y2 被传递给 Qt 方法,如QQuickView::setPosition But....

因为我们使用QT_SCALE_FACTOR环境变量来让应用程序缩放主显示器 DPI,所以所有 QScreen 几何图形都将具有与 Windows 用作屏幕坐标系不同的标准化坐标。因此,如果环境变量不是,则上面计算的最终窗口位置坐标X2,Y2不会映射到 Qt 坐标中的预期位置QT_SCALE_FACTOR1.0

最终修复以计算 Qt 窗口的最终顶部/左侧位置。

  • 调用EnumDisplayMonitors并枚举监视器列表。找到X2,Y2上面讨论的显示器所在的位置。将几何图形保存在一个MONITORINFOEX.szDevice名为MONITORINFOEX.rcMonitor的变量中rect

  • Call QGuiApplication::screens()并枚举这些对象以查找其name()属性与MONITORINFOEX.szDevice上一步中的 相匹配的 QScreen 实例。然后将this 方法QRect返回的值保存到一个名为. 将 QScreen 保存到一个名为的指针变量中geometry()QScreenqRectpScreen

X2,Y2转换为的最后一步XFinal,YFinal是这个算法:

XFinal  =       (X2 - rect.left) * qRect.width
                -------------------------------  + qRect.left
                           rect.width

YFinal  =       (Y2 - rect.top) * qRect.height
                -------------------------------  + qRect.top
                           rect.height

这只是屏幕坐标映射之间的基本插值。

那么最终的窗口定位就是在Qt的view对象上同时设置QScreen和XFinal,YFinal的位置。

QPoint position(XFinal, YFinal);
pView->setScreen(pScreen);
pView->setPosition(position);

考虑的其他解决方案:

可以在 QGuiApplication 对象上设置称为Qt::AA_EnableHighDpiScaling的 Qt 模式。它为您完成了上述大部分工作,除了它强制所有缩放为一个积分比例因子(1x、2x、3x 等......从不 1.5 或 1.75)。这对我们不起作用,因为在 DPI 设置为 150% 时将窗口缩放 2 倍看起来太大了。

于 2018-06-01T08:47:32.120 回答