15

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

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

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

许多建议说根本不使用尺寸,而是使用浮动布局。但在许多情况下,至少需要存在最小宽度和/或最小高度。由于 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 应用程序?

4

1 回答 1

12

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

我们不依赖 Qt 在每显示器 DPI 感知模式下进行自动缩放。至少带有Qt::AA_EnableHighDpiScalingset 的基于 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应用程序启动)。我们希望 Qt 公司意识到需要针对小部件自动缩放的每显示器 DPI 感知模式。为什么我们需要它(?)是另一个问题。就我而言,我在自己的应用程序小部件画布中进行了每个监视器的渲染,该画布应该可以缩放。

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

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

然后我阅读了关于如何应用多个屏幕因素的Qt 博客,并尝试对首先列出 4K 的 4K 和 1080p 显示器执行以下操作(主要)。

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

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

于 2016-10-03T04:00:28.427 回答