如何着手开发新的 Qt 5.7+ High-DPI Per Monitor DPI Aware 应用程序?

How to approach development of new Qt 5.7+ High-DPI Per Monitor DPI Aware application?

我已经阅读了官方 Qt documentation 以及 Whosebug 上关于 Qt 中高 DPI 支持的许多文章和问题。他们都专注于移植旧应用程序并使它们在尽可能少的更改下工作。

但是如果我要启动一个全新的应用程序,以支持每个显示器 DPI 感知应用程序,最好的方法是什么?

如果我没理解错的话,Qt::AA_EnableHighDpiScaling与我想要的恰恰相反。我实际上应该禁用 HighDpiScaling 并在运行时手动计算所有维度?

很多建议都说根本不要使用尺寸,要使用浮动布局。但在许多情况下,至少需要最小宽度 and/or 最小高度。由于 Qt Designer 只允许我将值放在绝对像素中,正确的方法是什么?如果显示器分辨率发生变化,我应该在哪里放置重新计算尺寸的代码?

还是我应该使用自动缩放?

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

在我尝试添加 HighDPI 支持的一个较旧的应用程序中,我使用了这种方法 - 列出 DOM 的所有子项,并按照给定的比例一个一个地调整它们的大小。 Ratio = 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();
    }

从 main 调用:

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 应用程序?

If I were to start a brand new application, with the intention to support per-monitor DPI awareness, what is the best approach?

我们不依赖 Qt 在每个监视器 DPI 感知模式下进行自动缩放。至少设置了 Qt::AA_EnableHighDpiScaling 的基于 Qt 5.7 的应用程序不会这样做,并且 'high DPI scaling' 更准确地绘制而不考虑像素密度。

并且要调用每个监视器的 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=.

If I understand correctly, Qt::AA_EnableHighDpiScaling is the very opposite of what I want. I should actually disable HighDpiScaling and calculate all the dimensions manually on runtime?

不,这不是对立面,而是不同的东西。有几个 Qt 错误作为无错误关闭:QTBUG-55449 and QTBUG-55510 that show the intent behind the feature. BTW, there is QTBUG-55510 提供了一个用于设置 Qt DPI 感知而不修复 qt.conf 的编程解决方法(自行决定使用,因为它使用 'private' Qt 实现类 更新的 Qt 版本更改界面,恕不另行通知)。

并且您表达了在每个显示器 DPI 感知模式下进行缩放的正确方法。不幸的是,当时没有太多选择。但是,当 window 缩放从一个显示器移动到另一个显示器时,有编程方法可以协助事件处理。应该使用类似 (Windows):

的方法调用此问题开头的 resizeWidget (一个,不是很多)
// 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 开始解决)。我们希望 Qt 公司意识到需要具有自动缩放小部件的每个监视器 DPI 感知模式。为什么我们需要它(?)是另一个问题。在我的例子中,我在自己的应用程序小部件 canvas 中进行了每个监视器的渲染,应该对其进行缩放。

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

QT_SCREEN_SCALE_FACTORS [list] specifies scale factors for each screen. This will not change the size of point sized fonts. This environment variable is mainly useful for debugging, or to work around monitors with wrong EDID information(Extended Display Identification Data).

然后我阅读了 Qt blog 如何应用多个屏幕因素,并尝试对 4K 和 1080p 显示器执行以下操作,其中 4K 列在最前面(主要)。

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

这确实有点帮助:几乎正确的渲染 但是 在将 window 从一台显示器移动到另一台显示器时引入了尺寸为 window 的缺陷QTBUG-55449 确实如此。我想如果客户认为当前的应用程序行为是一个错误,我会采用 WM_DPICHANGED + QT_SCREEN_SCALE_FACTORS 方法(我们通过 System DPI Aware 为所有显示器 DPI 创建相同的基础)。仍然没有现成的 Qt 解决方案。