Как подойти к разработке нового приложения Qt 5.7+ с высоким разрешением на монитор для DPI Aware?

Я прочитал официальную Qt-документацию и много статей и вопросов о StackOverflow о высокой поддержке DPI в Qt. Все они сосредоточены на переносе старых приложений и обеспечении их работы с минимальными изменениями.

Но если я должен был создать совершенно новое приложение, с намерением поддержать приложение с поддержкой 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

Если бы я начал новую заявку с намерением поддержка на уровне мониторинга 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 - это напротив того, что я хочу. Я должен фактически отключить HighDpiScaling и рассчитать все размеры вручную во время выполнения?

Нет, это не противоположность, а другое. Есть пара ошибок Qt, которые были закрыты как no-bugs: QTBUG-55449 и QTBUG-55510, которые показывают намерение этой функции. BTW, QTBUG-55510 предоставляется программный обход для настройки Qt-DPI-осведомленности без фиксации Qt.conf (используйте по своему усмотрению, потому что он использует 'private' классы реализации 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;

Это довольно сложный и трудный путь, и я прибегал к тому, чтобы приложение переключалось между режимами System и Per Monitor DPI Aware, позволяя пользователю выбирать режим и перезапускать процесс приложения (либо фиксируя Qt.conf, либо делая обход из QTBUG-55510 в начале приложения). Наша надежда заключается в том, что компания Qt понимает, что для режима мониторинга DPI необходим режим автоматического масштабирования для виджетов. Зачем нам это нужно (?) - еще один вопрос. В моем случае у меня есть рендеринг для каждого монитора в собственном холсте виджета приложения, который должен быть масштабирован.

Сначала, прочитав комментарий к этому вопросу от @selbie, я понял, что есть способ попытаться установить QT_SCREEN_SCALE_FACTORS во время запуска приложения:

QT_SCREEN_SCALE_FACTORS [список] определяет масштабные коэффициенты для каждого экран. Это не изменит размер шрифтов с размерами точек. Эта переменная окружения в основном полезна для отладки или для работы мониторы с неправильной информацией EDID (расширенная идентификация дисплея Данные).

Затем я прочитал блог Qt о том, как применять множественные факторы экрана, и попытался сделать это ниже для мониторов 4K и 1080p, где первые 4K перечислены первыми (основной).

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

Это немного помогает: почти правильный рендеринг, но вводит дефекты с размером окна при перемещении окна с одного монитора на другой, в значительной степени похожим на QTBUG-55449 делает. Думаю, я пойду с подходом WM_DPICHANGED + QT_SCREEN_SCALE_FACTORS, если клиент рассмотрит текущее поведение приложения как ошибку (мы делаем одну и ту же базу для всех мониторов DPI через System DPI Aware). Еще нет готового к использованию решения от Qt.