Как подойти к разработке нового приложения Qt 5.7+ High-DPI Per Monitor DPI Aware?

Я прочитал официальную документацию Qt, а также множество статей и вопросов на StackOverflow о высоком разрешении на дюйм. поддержка в 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();
    }

который вызывается из основного:

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?


person michnovka    schedule 03.10.2016    source источник
comment
В Win32 мы добились успеха, просто запросив у ОС (GetDeviceCaps) параметр dpi монитора, разделив его на 96,0 и задав соответствующую переменную env QT_SCALE_FACTOR. Возможны компромиссы. На Mac нам не нужно было ничего делать. YMMV.   -  person selbie    schedule 03.10.2016
comment
@selbie, как настроить масштабирование адреса QT_SCALE_FACTOR для каждого монитора?   -  person Alexander V    schedule 03.10.2016
comment
Возможно, этот [список] QT_SCREEN_SCALE_FACTORS определяет коэффициенты масштабирования для каждого экрана. Это не изменит размер шрифтов с размером точки. Эта переменная среды в основном полезна для отладки или для обхода мониторов с неправильной информацией EDID (расширенные данные идентификации дисплея). НО не уверен, кто-нибудь хочет попробовать установить его из приложения?   -  person Alexander V    schedule 03.10.2016
comment
@AlexanderVX - шрифты отлично масштабируются с QT_SCALE_FACTOR, если вы используете font.pixelSize для установки. Это не просто инструмент отладки, это единственный способ отправить приложение QML без необходимости загрязнять код QML какой-то функцией масштабирования для каждого литерального числа. QT_SCREEN_SCALE_FACTORS тоже может работать. Но переменные среды считываются только один раз при инициализации Qt.   -  person selbie    schedule 03.10.2016


Ответы (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 закрыта как отсутствие ошибок: 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;

Это довольно сложный и неприятный путь, и я прибегнул к тому, чтобы приложение могло переключаться между режимами 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 пока нет.

person Alexander V    schedule 03.10.2016
comment
Спасибо за твой ответ. отсутствие поддержки простого решения с высоким разрешением, как в программировании для Android, сводит меня с ума. есть ли что-то, что я должен добавить к моей функции resizeWidgets? Знаете ли вы какое-либо демонстрационное приложение с открытым исходным кодом с функциями Qt highdpi? спасибо - person michnovka; 03.10.2016
comment
@TomášNavara еще не знает о решении с открытым исходным кодом для этого. Я попытался, и этот путь оказался минным полем из-за слишком большого количества рутинных масштабирований. Вы уже показываете понимание того, что нужно сделать программно, но в Windows вы можете воспользоваться WM_DPICHANGED, МОЖЕТ БЫТЬ, что установка QT_SCREEN_SCALE_FACTORS [список] будет работать, НО для уже подключенных мониторов при запуске приложения. Я добавил пару утверждений для этого. - person Alexander V; 03.10.2016
comment
поэтому просто уточню - какое преимущество имеет указание dpiawareness = 2, когда я сам делаю всю работу изнутри кода при получении события dpichange? а как же линукс? - person michnovka; 03.10.2016
comment
Вы сказали о намерении поддерживать приложение с поддержкой DPI для каждого монитора. Вот как вызывается режим Per-Monitor DPI Aware. Или посмотрите обходной путь QTBUG-55510 с параметром 2. Или прочитайте это msdn.microsoft.com/en-us/library/windows/desktop/ Смысл этого режима в том, что Windows передает событие WM_DPICHANGED в процесс приложения. И MacOS делает то же самое. - person Alexander V; 03.10.2016