如何开发新的Qt 5.7+高DPI Per Monitor DPI Aware应用程序?

15

我已阅读官方Qt文档以及StackOverflow上关于Qt高DPI支持的许多文章和问题。所有这些都集中在移植旧应用程序并尽可能少地进行更改,但如果我要开始一个全新的应用程序,并打算支持每个监视器DPI感知应用程序,最佳方法是什么?

如果我理解正确,Qt :: AA_EnableHighDpiScaling 恰恰相反。实际上,我应该禁用HighDpiScaling并在运行时手动计算所有尺寸吗?

许多建议说根本不使用大小,而使用浮动布局。但在许多情况下,至少希望具有最小宽度和/或最小高度存在。由于Qt Designer只允许我放置绝对像素值,因此应该采取什么正确的方法?如果监视器分辨率发生变化,应将代码放在何处以重新计算尺寸?

还是应该选择自动缩放?

我之前Qt应用程序的解决方案(经过不充分测试)

在我尝试添加HighDPI支持的较旧应用程序之一中,我使用了此方法 - 列出DOM的所有子项,并按比例逐一调整其大小。比率= 1会产生与我在Qt Designer中指定的尺寸相等的维度。

    void resizeWidgets(MyApp & qw, qreal mratio)
    {

        // ratio to calculate correct sizing
        qreal mratio_bak = mratio;

        if(MyApp::m_ratio != 0)
            mratio /= MyApp::m_ratio;

        // this all was done so that if its called 2 times with ratio = 2, total is not 4 but still just 2 (ratio is absolute)
        MyApp::m_ratio = mratio_bak;

        QLayout * ql = qw.layout();

        if (ql == NULL)
            return;

        QWidget * pw = ql->parentWidget();

        if (pw == NULL)
            return;

        QList<QLayout *> layouts;

        foreach(QWidget *w, pw->findChildren<QWidget*>())
        {
            QRect g = w->geometry();

            w->setMinimumSize(w->minimumWidth() * mratio, w->minimumHeight() * mratio);
            w->setMaximumSize(w->maximumWidth() * mratio, w->maximumHeight() * mratio);

            w->resize(w->width() * mratio, w->height() * mratio);
            w->move(QPoint(g.x() * mratio, g.y() * mratio));
            
        }

        foreach(QLayout *l, pw->findChildren<QLayout*>())
        {
            if(l != NULL && !(l->objectName().isEmpty()))
                layouts.append(l);
        }
        
        foreach(QLayout *l, layouts) {
            QMargins m = l->contentsMargins();

            m.setBottom(m.bottom() * mratio);
            m.setTop(m.top() * mratio);
            m.setLeft(m.left() * mratio);
            m.setRight(m.right() * mratio);

            l->setContentsMargins(m);

            l->setSpacing(l->spacing() * mratio);

            if (l->inherits("QGridLayout")) {
                QGridLayout* gl = ((QGridLayout*)l);

                gl->setHorizontalSpacing(gl->horizontalSpacing() * mratio);
                gl->setVerticalSpacing(gl->verticalSpacing() * mratio);
            }

        }
        
        QMargins m = qw.contentsMargins();

        m.setBottom(m.bottom() * mratio);
        m.setTop(m.top() * mratio);
        m.setLeft(m.left() * mratio);
        m.setRight(m.right() * mratio);

        // resize accordingly main window
        qw.resize(qw.width() * mratio, qw.height() * mratio);
        qw.setContentsMargins(m);
        qw.adjustSize();
    }

从主函数调用:

int main(int argc, char *argv[])
{

    QApplication a(argc, argv);
    MyApp w;

    // gets DPI
    qreal dpi = a.primaryScreen()->logicalDotsPerInch();

    MyApp::resizeWidgets(w, dpi / MyApp::refDpi);

    w.show();

    return a.exec();
}

我不认为这是一个好的解决方案。鉴于我是从头开始,并且可以完全自定义我的代码以符合最新的Qt标准,那么我应该采用什么方法来获取HighDPI应用程序?


在Win32上,我们通过查询操作系统(GetDeviceCaps)获取显示器dpi设置的方式取得了成功,将其除以96.0,并设置环境变量QT_SCALE_FACTOR以匹配。这里存在一些权衡。在Mac上,我们不需要做任何事情。你的情况可能会有所不同。 - selbie
@selbie,请问如何设置QT_SCALE_FACTOR地址以实现每个显示器的缩放? - Alexander V
也许这个QT_SCREEN_SCALE_FACTORS [list]指定了每个屏幕的缩放因子。这不会改变点大小字体的大小。这个环境变量主要用于调试,或者解决具有错误EDID信息(扩展显示标识数据)的监视器问题。但是不确定,有人愿意尝试从应用程序内部设置它吗? - Alexander V
@AlexanderVX - 如果您使用font.pixelSize进行设置,则字体可以很好地与QT_SCALE_FACTOR比例缩放。这不仅是一种调试工具,而且是在不必为每个文字数字污染QML代码的情况下发布QML应用程序的唯一方法。QT_SCREEN_SCALE_FACTORS也可以起作用。但是环境变量仅在Qt初始化时读取一次。 - selbie
1个回答

12
如果我要开始一个全新的应用程序,希望支持每个显示器DPI感知,最好的方法是什么?
我们不依赖Qt进行自动缩放以支持每个显示器DPI感知模式。至少使用设置了Qt :: AA_EnableHighDpiScaling的Qt 5.7应用程序不会这样做,“高DPI缩放”更多地是准确绘制而不考虑像素密度。
而要调用每个显示器DPI感知模式,您需要修改Qt.conf文件,该文件位于与项目可执行文件相同的目录中:
[Platforms]
# 1 - for System DPI Aware
# 2 - for Per Monitor DPI Aware
WindowsArguments = dpiawareness=2

# May need to define this section as well
#[Paths]
#Prefix=.

如果我理解正确,Qt::AA_EnableHighDpiScaling恰恰相反于我所需要的。我实际上应该禁用HighDpiScaling并在运行时手动计算所有维度吗? 不,它不是相反的,而是一个不同的东西。有一些被关闭为没有错误的Qt漏洞:QTBUG-55449QTBUG-55510,这表明了这个特性背后的意图。顺便说一句QTBUG-55510,提供了一个编程解决方案来设置Qt DPI感知而不修复qt.conf(使用时要自行决定,因为它使用“私有”Qt实现类,这些类会在较新版本的Qt中更改接口而没有任何通知)。

您提到了在单独监视器DPI感知模式下进行缩放的正确方法。不幸的是,目前没有太多其他选择。但是,当窗口从一个监视器移动到另一个监视器时,有编程方法可以帮助事件处理。例如,在此问题开头处的resizeWidget(一个而不是多个)方法应该使用类似以下的方式调用(Windows):

// we assume MainWindow is the widget dragged from one monitor to another
bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, long* result)
{
   MSG* pMsg = reinterpret_cast<MSG*>(message);

   switch (pMsg->message)
   {
      case WM_DPICHANGED:
         // parameters TBD but mind that 'last' DPI is in
         // LOWORD(pMsg->wParam) and you need to detect current
         resizeWidget(monitorRatio());
         break;

这是一种相当困难和麻烦的方法,我采用了让用户选择模式并重新启动应用程序进程(修复qt.conf或从QTBUG-55510中进行解决方法)来使应用程序能够在系统和每个监视器DPI感知模式之间切换。我们希望Qt公司意识到需要为小部件提供自动缩放的每个监视器DPI感知模式。为什么我们需要它(?)是另一个问题。在我的情况下,我在自己的应用程序小部件画布内进行每个监视器渲染,因此需要进行缩放。

起初,阅读@selbie对这个问题的评论,我意识到也许有一种方法可以在应用程序启动时尝试设置QT_SCREEN_SCALE_FACTORS:

QT_SCREEN_SCALE_FACTORS [list]指定每个屏幕的比例因子。 这不会改变点大小的字体的大小。这个环境变量主要用于调试, 或者解决具有错误EDID信息(扩展显示标识数据)的监视器问题。

我接着阅读了Qt博客上有关如何应用多个屏幕因素的内容,并尝试根据以下步骤对4K和1080p显示器进行调整,其中4K显示器排在首位(主要)。

qputenv("QT_SCREEN_SCALE_FACTORS", "2;1");

我能稍微帮一下忙:几乎正确的渲染,但是在将窗口从一个显示器移动到另一个显示器时会出现大小问题,就像QTBUG-55449一样。如果客户认为当前应用程序行为有问题(我们通过系统DPI感知使所有监视器DPI具有相同的基础),那么我想我会采用WM_DPICHANGED+QT_SCREEN_SCALE_FACTORS方法。但是,Qt目前还没有可用的解决方案。


谢谢你的回答。缺乏像Android编程中那样简单的高DPI解决方案的支持让我感到疯狂。我应该在我的resizeWidgets函数中添加什么吗?你知道有没有带有Qt高DPI功能的开源演示应用程序吗?谢谢。 - michnovka
@TomášNavara 目前还不知道有开源解决方案。我尝试过,但这种方式会涉及到太多例行的缩放操作,非常棘手。你已经表现出了对编程需求的理解,但在 Windows 中,你可以利用 WM_DPICHANGED,也许设置 QT_SCREEN_SCALE_FACTORS [list] 可以起作用,但是只适用于启动应用程序时已连接的监视器。我已经添加了一些语句来实现这个功能。 - Alexander V
只是为了澄清一下 - 当我在接收到dpichange事件后自己从代码内部完成所有工作时,指定dpiawareness = 2有什么优势?那Linux呢? - michnovka
1
你说过“支持每个显示器的 DPI 感知应用程序”的意图。这就是 Per-Monitor DPI Aware 模式的调用方式。或者使用参数 2 的 QTBUG-55510 解决方法。或者阅读此链接 https://msdn.microsoft.com/en-us/library/windows/desktop/dn280512(v=vs.85).aspx 。该模式的含义是 Windows 将 WM_DPICHANGED 事件传递给应用程序进程。而 MacOS 也有类似的功能。 - Alexander V

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接