如何开发新的Qt 5.7+高DPI每监视器DPI感知应用程序?

时间:2016-10-03 02:42:58

标签: c++ qt highdpi

我已经阅读了有关Qt中高DPI支持的官方Qt documentation和许多关于StackOverflow的文章和问题。所有这些都专注于移植旧应用程序并使它们尽可能少地进行更改。

但如果我要开始一个全新的应用程序,打算支持每个监视器的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应用程序?

1 个答案:

答案 0 :(得分:8)

  

如果我要开始一个全新的应用程序,意图   支持每个监控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感知模式下进行扩展的正确方法。不幸的是,当时没有其他选择。但是,当程序从一个监视器移动到另一个监视器时,有程序化的方法来协助事件处理窗口缩放。应该使用类似(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)来启用应用程序在系统和每个监视器DPI感知模式之间切换或者在应用程序启动时从QTBUG-55510执行解决方法)。我们希望Qt公司意识到需要为每个监视器DPI识别模式以及小部件的自动扩展。我们为什么需要它(?)是另一个问题。在我的情况下,我在自己的应用程序小部件画布中有每个监视器渲染,应该缩放。

首先,从@selbie阅读此问题的评论我意识到在应用程序启动时可能有办法尝试设置QT_SCREEN_SCALE_FACTORS:

  

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

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

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

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