如何在QML中正确保存窗口状态

时间:2016-12-21 01:04:13

标签: qt qml qmainwindow qt5.6

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

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

在进入更多细节之前,我的问题的根源是支持具有不同分辨率的监视器的多个监视器环境。

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

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

1 个答案:

答案 0 :(得分:3)

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

<强> TLDR

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

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

问题:

当有多个监视器和多个DPI分辨率来对抗时,很难对Qt窗口进行绝对定位。

我们的应用程序窗口&#34;弹出&#34;从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()));    

上面的代码采用了主监视器&#34; DPI scale&#34;并告诉Qt匹配它。它具有让Qt本地计算所有缩放而不是像Windows在非DPI感知应用程序中那样的位图扩展的令人愉快的效果。

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

在单一监视器场景中,一切正常。只要两台显示器上的DPI相同,它甚至可以在多监视器场景下正常工作。但是在具有不同DPI的多个监视器的配置上,事情已经开始了。如果由于屏幕更换或投影仪插入(或拔掉插头)而导致窗口不得在非主显示器上弹出,则会发生奇怪的事情。 Qt窗口将出现在错误的位置。或者在某些情况下,窗口内的内容会缩放不正确。当它确实有效时,会有一个太大的问题。或者&#34;太小&#34;当位于以不同DPI运行的类似大小的监视器上时,窗口缩放到一个DPI的问题。

我的初步调查显示Qt的不同QScreens几何形状的坐标空间被忽略了。每个QScreen rect的坐标基于QT_SCALE_FACTOR进行缩放,但各个QScreen rects的相邻轴未对齐。例如一个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上此High DPI programming部分中的所有内容。好读。

这是我们所做的。

如上所述保持QT_SCALE_FACTOR的初始化。

然后我们将我们的进程和Qt的初始化从每个监视器的DPI感知切换到系统感知的DPI。 system-dpi的好处在于,当监视器从其下方更改时,它允许Windows将应用程序窗口自动缩放到预期大小。 (所有Windows API都表现为所有监视器都具有相同的DPI)。如上所述,当DPI与主监视器不同时,Windows正在进行位图扩展。所以有一个&#34;模糊问题&#34;与监视器切换抗衡。但它确实比以前更好!

默认情况下,Qt会尝试将进程初始化为每个监视器识别的应用程序。要强制它在system-dpi感知中运行,请在Qt初始化之前,在应用程序启动时尽早调用值SetProcessDpiAwareness PROCESS_SYSTEM_DPI_AWARE。在此之后,Qt无法改变它。

刚刚切换到系统感知dpi修复了许多其他问题。

最终错误修复:

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

但是,在Windows 10上从Shell_NotifyIconGetRect返回的坐标将始终是&#34;每个监视器识别&#34;本机坐标,而不是缩放到系统DPI。使用PhysicalToLogicalPointForPerMonitorDPI进行转换。此API在Windows 7上不存在,但不需要它。如果您支持Windows 7,请使用LoadLibraryGetProcAddress此API。如果API不存在,请跳过此步骤。使用PhysicalToLogicalPointForPerMonitorDPIX1,Y1转换为系统感知的DPI坐标,然后拨打X2,Y2

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

由于我们使用QT_SCALE_FACTOR环境变量来使应用程序扩展主监视器DPI,因此所有QScreen几何都将具有与Windows用作屏幕坐标系不同的标准化坐标。因此,如果X2,Y2环境变量不是QT_SCALE_FACTOR

,则上面计算的1.0的最终窗口位置坐标将不会映射到Qt坐标中的预期位置

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

  • 调用EnumDisplayMonitors并枚举监视器列表。找到上面讨论X2,Y2所在的监视器。保存MONITORINFOEX.szDevice以及MONITORINFOEX.rcMonitor

  • 变量中的rect几何图形
  • Call QGuiApplication::screens()并枚举这些对象以查找name()属性与上一步中的MONITORINFOEX.szDevice匹配的QScreen实例。然后将此QRect的{​​{1}}方法返回的geometry()保存到名为QScreen的变量中。将QScreen保存到名为qRect

  • 的指针变量中

pScreen转换为X2,Y2的最后一步是此算法:

XFinal,YFinal

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

然后最终窗口定位是在Qt的视图对象上设置QScreen和XFinal,YFinal位置。

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

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

考虑的其他解决方案:

可以在QGuiApplication对象上设置名为Qt::AA_EnableHighDpiScaling的Qt模式。它为你做了很多以上的事情,除了它强制所有缩放都是一个整数比例因子(1x,2x,3x等......从不1.5或1.75)。这对我们没有用,因为在DPI设置为150%时窗口的2倍缩放看起来太大了。