Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

qt

highdpi

I have read the official Qt documentation and many articles and questions on StackOverflow about high DPI support in Qt. All of them focus on porting old applications and making them work with as least changes as possible.

But if I were to start a brand new application, with the intention to support per-monitor DPI aware app, what is the best approach?

If I understand correctly, Qt::AA_EnableHighDpiScaling is the very opposite of what I want. I should actually disable HighDpiScaling and calculate all the dimensions manually on runtime?

Many of the suggestions say not to use sizes at all, to use floating layouts. But in many cases at least minimum width and/or minimum height is desired to be present. As Qt Designer only allows me to put values in absolute pixels, what is the right approach? Where should I place the code to recalculate dimensions if monitor resolution changes?

Or should I just go with the automatic scaling?

My solution from previous Qt app (not well tested)

In one of my older apps where I tried to add HighDPI support, I used this approach - list all children of DOM and resize them one by one given some ratio. Ratio = 1 would produce dimensions equal to the ones I specified in 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();
    }

which is called from 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();
}

I don't consider this a good solution. Given that I am starting fresh and I can fully customise my code to the latest Qt standards, what approach should I use to get HighDPI apps?

like image 629
michnovka Avatar asked Oct 03 '16 02:10

michnovka


People also ask

How do I make my application DPI aware?

To make your application dpi-aware, you must cancel automatic dpi scaling, and then adjust user interface elements to scale appropriately to the system dpi.

What is DPI awareness mode?

Desktop applications on Windows can run in different DPI awareness modes. These modes enable different DPI scaling behavior and can use different coordinate spaces. For more information on DPI awareness, see High DPI Desktop Application Development on Windows.

What is a high DPI monitor?

High DPI displays have increased pixel density, compared to standard DPI displays. Pixel density is measured in Dots per Inch (DPI) or Pixels per Inch (PPI), and is determined by the number of display pixels and their size.

What is DPI in app?

This content is targeted at developers who are looking to update desktop applications to handle display scale factor (dots per inch, or DPI) changes dynamically, allowing their applications to be crisp on any display they're rendered on.

What DPI level should I Set my Qt application to?

By default, Qt applications are set to Per-Monitor DPI Aware on Windows 8.1 or System-DPI Aware on older Windows versions. As of Qt 5.4, this level can be specified via a parameter to the platform plugin: For more information, see Using qt.conf. Qt provides the following ways for you to handle high DPI support in your application.

What is DPI awareness in Qt?

An application on Windows can assume one of the following levels of "DPI Awareness" (From the Qt documentation) : DPI Unaware: This level has been introduced in Windows-Vista. Windows will pretend to the application that it is running on a standard display of 96 DPI of 1920x1080 and scale the application accordingly.

Does Qt support high-DPI displays?

Fortunately there is support for high-DPI displays since Qt 5.4 as there has been many high-DPI issue fixes. An application on Windows can assume one of the following levels of "DPI Awareness" (From the Qt documentation) : DPI Unaware: This level has been introduced in Windows-Vista.

How to get high DPI scaling with multi monitor awareness?

The MSDN functions is called SetProcessDPIAware (). Assuming you are given some Qt (plus OSG/OpenGl) application, you can achieve nice high DPI scaling and multi-monitor awareness at the same time.


1 Answers

If I were to start a brand new application, with the intention to support per-monitor DPI awareness, what is the best approach?

We don't rely on Qt for automatic scaling in per-monitor DPI aware mode. At least Qt 5.7-based app with Qt::AA_EnableHighDpiScaling set does not do that and 'high DPI scaling' is more of accurate drawing regardless of pixel density.

And to invoke per-monitor DPI aware mode you need to modify Qt.conf file at the same directory where you project executable file is:

[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=.

If I understand correctly, Qt::AA_EnableHighDpiScaling is the very opposite of what I want. I should actually disable HighDpiScaling and calculate all the dimensions manually on runtime?

No, it is not an opposite but a different thing. There is a couple of Qt bugs closed as no-bugs: QTBUG-55449 and QTBUG-55510 that show the intent behind the feature. BTW, there is QTBUG-55510 a programmatic workaround for setting Qt DPI awareness without fixing qt.conf is provided (use at own discretion because it uses 'private' Qt implementation classes that change the interface without any notice with newer Qt version).

And you expressed the correct approach to do the scaling in per-monitor DPI aware mode. Unfortunately except that there is no much alternative at the time. There programmatic ways to assist the event handling for the window scaling when it is moved from one monitor to another, though. The method like resizeWidget (one, not many) at the head of this question should be called using something like (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;

This is quite difficult and troublesome way to go and I resorted to enable the app to switch between System and Per Monitor DPI Aware modes by letting the user to choose the mode and restart the app process (either fixing qt.conf or doing workaround from QTBUG-55510 at the app start). Our hope is that Qt company realizes there is a need for per-monitor DPI aware mode with auto scaling for widgets. Why would we need it (?) is another question. In my case I have per-monitor rendering within own app widget canvas which supposed to be scaled.

At first, reading the comment to this question from @selbie I realized maybe there is a way to try to set QT_SCREEN_SCALE_FACTORS while the app starts:

QT_SCREEN_SCALE_FACTORS [list] specifies scale factors for each screen. This will not change the size of point sized fonts. This environment variable is mainly useful for debugging, or to work around monitors with wrong EDID information(Extended Display Identification Data).

I then read Qt blog on how to apply multiple screen factors and attempted to do the below for 4K and 1080p monitors where 4K is listed first (main).

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

That does helps a bit: almost correct rendering but introduces defects with window size while moving the window from one monitor to another pretty much like QTBUG-55449 does. I guess I will go with WM_DPICHANGED + QT_SCREEN_SCALE_FACTORS approach if the customer considers current app behavior as a bug (we make the same base for all monitors DPI via System DPI Aware). Still there is no ready to use solution from Qt yet.

like image 105
Alexander V Avatar answered Oct 06 '22 23:10

Alexander V