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

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

本文关键字:DPI 监视器 应用程序 感知 Qt 何开发 开发      更新时间:2023-10-16

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

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

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

许多建议说不要使用大小,使用浮动布局。但在许多情况下,至少需要最小宽度和/或最小高度。Qt设计器只允许我把绝对像素的值,什么是正确的方法?如果监视器分辨率发生变化,我应该将重新计算尺寸的代码放在哪里?

或者我应该直接使用自动缩放?

我的解决方案从以前的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应用程序?

如果我要启动一个全新的应用程序,意图支持每个监视器的DPI感知,最好的方法是什么?

我们不依赖Qt在每个监视器DPI感知模式下进行自动缩放。至少Qt 5.7基于Qt::AA_EnableHighDpiScaling设置的应用程序不会这样做,"高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是非常和我想要的相反。我应该禁用highdpcaling在运行时手动计算所有维度?

不,它不是一个相反的东西,而是一个不同的东西。有几个Qt bug被关闭为无bug: QTBUG-55449和QTBUG-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;

这是相当困难和麻烦的方式去,我采取使应用程序之间切换系统和每监视器DPI感知模式,让用户选择的模式和重新启动应用程序进程(要么修复qt.conf或做解决方案从QTBUG-55510在应用程序启动)。我们希望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所做的那样。如果客户认为当前的应用行为是一个bug,我想我会使用WM_DPICHANGED + QT_SCREEN_SCALE_FACTORS方法(我们通过系统DPI感知为所有监视器DPI制作相同的基础)。仍然没有准备好使用Qt的解决方案。