Posts Tagged ‘QTimer’

Tip for resizing Qt Windows While Retaining Good User Responsiveness

Written by rfelten. Posted in Qt

Qt logoIn general, there are a variety of ways to set the sizes and positions of individual and grouped widgets within a window, and its various layouts. If the user resizes the window manually, and you are happy with how Qt redraws the window, then you don’t have to do anything else. The user can resize the window to his hearts content, it will respond quickly, and it will always look great.

Suppose, however, that the size of some of the widgets have been computed dynamically. Most likely those calculations depended on the size of the original window. In this case, you might want to recompute their sizes whenever the window is resized. For example, in a recent program I have written, there are a variable number of colored rectangles (derived from a QLineEdit widget) that represent the size of a namespace within a solid state hard drive.  These widgets are drawn across the width of the window so that they all are shown no matter the width of the window. If the window is resized to be smaller, each of the widgets are drawn proportionally smaller. If the window is resized wider, each object is redrawn proportionally wider.  If I did not recompute the widths of each one of these objects, then each time the window is redrawn there would be a problem. If the window was resized narrower, the rectangles on the right hand side would no longer be visible, and if the window was resized wider there would be blank empty space on the right hand side, which is not what I want. There can be many rows of these objects, so quite a few rectangles need to be recomputed each time the window is resized. Similarly, there are plots, gauges, and other widgets who’s sizes also depend on the width and height of the window.

In order to detect when the window is resized, you can use the event handler QWidget::resizeEvent(). This gets called every time the user resizes the window. In resizeEvent(), you can recompute the size of the widgets. Unfortunately, resizeEvent() gets called repeatedly as the user drags the edge of the window, and your computations might not be able to keep up. The result is a display that is not properly responsive to the dragging; instead it seems to lag behind.

Bear in mind that when interacting with people, you need to have a certain responsiveness in your software. According to Jeff Johnson’s GUI Bloopers 2.0, “0.1 seconds is the limit for perception of cause-and-effect between events. If software waits longer than 0.1 second to show a response to your action, cause-and-effect is broken; the software’s reaction will not seem to be a result of your action. … If an object the user is ‘dragging’ lags more than 0.1 second behind the cursor, users will have trouble placing it.” What this means is, that if you can’t guarantee that you can recompute and redraw the window in less than 0.1 second, the window’s resizing will fall behind the user’s attempts to resize it, and he will overshoot the mark (either when making it smaller or larger). It will be impossible for him to size the window exactly where he wants it, and he will hate you. Also bear in mind that the user’s computer is probably older, slower, busier, with less memory than yours. So even though your application may seem responsive on your computer, make sure you test it on slower computers that are also streaming music and running other background programs simultaneously.

The solution to this problem is to let Qt handle the resizing without recomputing the widget sizes, even though the widgets will temporarily not be the sizes you want them to be. You only need to recompute the widget’s sizes when the user stops dragging the window to resize it. I have found through trial and error that if you can’t meet the 0.1 responsiveness goal, you can at least recompute the widget sizes when the user has stopped dragging for as much as 1/4 second. This gives you an additional .15 seconds of time to do your computations and your program will still seem responsive to the user, allowing him to resize the window as he pleases. This is because Qt is drawing the window at a fast rate, it’s just not redrawing all the widgets in their final sizes until you stop dragging the window. It looks and feels very natural. Of course, if you can’t do the computations in less than 1/4 second you’ll have to compromise further.

To do this, in the MainWindow class, override resizeEvent(), create a QTimer, and slot to handle the timer’s timeouts which will recompute the widgets sizes.

static const int RESIZE_TIMEOUT = 250; // 1/4 second in milliseconds
QTimer* resizeTimer;
void resizeEvent( QResizeEvent * event );
public slots: 
void resizeTimeout();

In mainwindow.cpp, at initialization, instantiate the timer, connect the timeout signal to the resizeTimeout slot

resizeTimer = new QTimer(this);
connect(resizeTimer, SIGNAL(timeout()), this, SLOT(resizeTimeout()));

In the function resizeEvent(), the timer is reset. As long as the function keeps getting called (i.e. the user is continuing to resize the window without a 1/4 second pause, the size of the widgets are not recomputed. The resizing action remains responsive to the user).

void MainWindow::resizeEvent( QResizeEvent * event )
{
    resizeTimer->stop();
    currentEvent = event;
    resizeTimer->start(RESIZE_TIMEOUT);
}

The slot resizeTimeout() stops the timer and recomputes the widgets based on the current width of the main window (after the user has resized it).

void MainWindow::resizeTimeout()
{
    resizeTimer->stop();
    int mainWindowWidth = this->width();
    ...
}

Finally, once the user is happy with the size of the window, why not save it for him, so next time he runs the program, it comes up in the same location and size that he left it last time he ran the program? Use QSettings, which will make this platform independent, so you don’t have to write separate code to save the information in the system registry on Windows, or a different method for linux or Mac. Here is an excerpt from Qt Help.

“The QSettings class provides persistent platform-independent application settings.

Users normally expect an application to remember its settings (window sizes and positions, options, etc.) across sessions. This information is often stored in the system registry on Windows, and in XML preferences files on Mac OS X. On Unix systems, in the absence of a standard, many applications (including the KDE applications) use INI text files.

QSettings is an abstraction around these technologies, enabling you to save and restore application settings in a portable manner.”

In the MainWindow class, define

QSettings* settings;

At initialization, restore the windows size and position if it had been previously saved. Otherwise, use the default size and position (which in my case, I prefer to override with a new default that I compute in a function called setWindowGeometry()).

    settings->beginGroup("MainWindow");
    if (settings->contains("size") && settings->contains("pos"))
    {
        resize(settings->value("size").toSize());
        move(settings->value("pos").toPoint());
    }
    else
    {
        setWindowGeometry();
    }
    settings->endGroup();

When the main window is closed, you need to override the close event, so you can save the window size and position in the settings file before the program is terminated.

void MainWindow::closeEvent(QCloseEvent *event)
 {
    settings->beginGroup("MainWindow");
    settings->setValue("size", size());
    settings->setValue("pos", pos());
    settings->endGroup();
}