gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-04 12:17:49 27 4
gpt4 key购买 nike

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

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

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

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

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

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

    最佳答案

    当我在几个月前评论我知道这些类型的问题时,@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 感知应用程序中所做的位图拉伸(stretch)的令人愉快的效果。

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

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

    我的初步调查显示,不同 QScreens 几何图形的 Qt 坐标空间看起来不对。每个 QScreen 矩形的坐标根据 QT_SCALE_FACTOR 进行缩放,但各个 QScreen 矩形的相邻轴没有对齐。例如一个 QScreen 矩形可能是 {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 在显示器更改时缩放它。

    一点背景。阅读 High DPI programming 本节中的所有内容在 MSDN 上。很好的阅读。

    这就是我们所做的。

    保留了 QT_SCALE_FACTOR 的初始化如上所述。

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

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

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

    最终错误修复:

    因为我们将窗口定位在 绝对位置 (在任务托盘中系统托盘图标的正上方),我们依赖于 Windows API, Shell_NotifyIconGetRect给我们系统托盘的坐标。一旦我们知道系统托盘的偏移量,我们就会计算窗口在屏幕上的顶部/左侧位置。让我们称这个位置 X1,Y1
    但是,坐标从 Shell_NotifyIconGetRect 返回在 Windows 10 上将始终是“每显示器感知” native 坐标,而不是缩放到系统 DPI。使用 PhysicalToLogicalPointForPerMonitorDPI转换。此 API 在 Windows 7 上不存在,但不需要。使用 LoadLibraryGetProcAddress如果您支持 Windows 7,请使用此 API。如果该 API 不存在,请跳过此步骤。使用 PhysicalToLogicalPointForPerMonitorDPI转换 X1,Y1到系统感知 DPI 坐标,我们将调用 X2,Y2 .

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

    因为我们使用的是 QT_SCALE_FACTOR环境变量来让应用程序缩放主监视器 DPI,所有 QScreen 几何图形都将具有与 Windows 用作屏幕坐标系不同的标准化坐标。所以最终的窗口位置坐标为 X2,Y2如果 QT_SCALE_FACTOR,上面计算的将不会映射到 Qt 坐标中的预期位置环境变量不是 1.0
    最终修复以计算 Qt 窗口的最终顶部/左侧位置。
  • 调用 EnumDisplayMonitors并枚举监视器列表。找到X2,Y2所在的监视器上面讨论的定位在。省下 MONITORINFOEX.szDevice以及 MONITORINFOEX.rcMonitor名为 rect 的变量中的几何图形
  • Call QGuiApplication::screens()并枚举这些对象以找到其 name() 的 QScreen 实例。属性匹配 MONITORINFOEX.szDevice在上一步中。然后保存掉QRectgeometry() 返回这个方法QScreen到一个名为 qRect 的变量中.将 QScreen 保存到名为 pScreen 的指针变量中

  • 转换的最后一步 X2,Y2XFinal,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);

    考虑的其他解决方案:

    有一种叫做 Qt::AA_EnableHighDpiScaling 的 Qt 模式可以在 QGuiApplication 对象上设置。它为您完成了上述大部分工作,除了它强制所有缩放比例为整数比例因子(1x、2x、3x 等...永远不会是 1.5 或 1.75)。这对我们不起作用,因为在 DPI 设置为 150% 的情况下,窗口的 2 倍缩放看起来太大了。

    关于qt - 如何在 QML 中正确保存窗口状态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41253624/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com