Fast update of many widgets in Qt GUI
multithreading, performance, qt, user-interface
Solution
One approach that has worked for me in the past is to build accessor methods into the worker object and then have the view "pull" the data based on its own update cycle.
To use your example code, add a method like this to `Generator`:
// TODO For a more complex class, you probably want one "get all the stats"
// accessor rather than lots of little methods -- that way all the data is
// synced up
int Generator::getCurrentValue()
{
QMutexLocker(mutex); // (also add a QMutex member to the class)
return value;
}
Then give the main window its own update timer that won't hammer the system very hard:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
// ...
// Replace dataAvailable code with this:
updateTimer = new QTimer;
connect(updateTimer, SIGNAL(timeout()), this, SLOT(updateDisplayedValues());
updateTimer->start(MY_MAIN_WINDOW_UPDATE_RATE); // some constant; try 200 ms?
// ...
}
void MainWindow::updateDisplayedValues()
{
int val = dataGenerator->getCurrentValue();
// TODO You might this more efficient by checking whether you *need to*
// repaint first here; generally Qt is pretty good about not wasting cycles
// on currently-hidden widgets anyway
for(int iCol=0; iCol< COL_MAX; iCol++)
{
for(int iRow=0; iRow<ROW_MAX; iRow++)
{
labels[iRow][iCol]->setText(QString::number(val));
}
}
}
Problem
I'm having difficulty understanding the best approach for an appliction that must display a large amount of data to the screen, which is being updated at a high rate. I'm writing this application in Qt for windows. I won't go in to details of the actual application, but I've written an example application below to demonstrate the problem. In this I have a thread which is calculating values. In this case it's one value which is simply a counter. In the real application its lots of values. This is updating once per millisecond. This rate is the required calculation rate of the data, not the required update rate for the GUI. As mentioned this is done in its own thread. The idea of this thread is that its just about the calculation of the data, and doesn't care about display of it. Now to update the display of the data in this example i'm using a grid of QLabels to display the value multiple times (simulating the display of many different values). I understand from the Qt documentation that the update of Widgets must be done in the main GUI thread. So what I do here is I get the thread calculatingthe values to emit a signal with the calculated value every time it recalculates it (1ms). This is then connected to the main GUI thred, which then updates each widget in turn to display the value. The conclusion from doing this is: - The GUI thread is being swamped by the 1ms update from the data thread, so the display becomes very slow to update. On my machine, with 100 widgets updating I estimate the update to be approx 4fps. - All other aspects of the GUI such as moving, resizing and button presses are all struggling to get processor time. - It seems that updating just a simple QLabel with text seems quite slow. Is this normal? - I've added timing code to this as well and the 1ms update in the GUI thread runs on avergae approx 1.8ms, with a max delta time of 40-75ms. So its running quite fast even though its getting behind. But presumably behind the scenes other events are going on the GUI thread event queue to actually paint updates to the screen and these are really struggling. - I don't really understand what is determining the actual screen update rate? How is Qt deciding when to update the screen here? Most importantly I'm not sure what the correct way to update the data to be displayed is. Clearly the screen doesn't need to update at 1ms and even if it could the monitor doesn't refresh that quick anyway. On the other hand I don't want my data thread to be concerning itself with the screen update rate. Is there a better way to get the data from the data thread to the GUi one without swamping the GUI event queue? Any insight into the Qt approach to this problem would be greatly appreciated. Here's the data generator that runs in its own thread: ``` class Generator : public QObject { Q_OBJECT public: explicit Generator(QObject *parent = 0); signals: void dataAvailable(int val); public slots: void run(bool run); void update(void); private: QTimer *timer; int value; }; void Generator::run(bool run) { if(run) { value = 0; timer = new QTimer; connect(timer, SIGNAL(timeout()), this, SLOT(update())); timer->start(1); } else { timer->stop(); delete timer; } } void Generator::update() { value++; emit dataAvailable(value); } ``` And here's the main GUI class that updates the dislay: ``` class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); private: QLabel *labels[ROW_MAX][COL_MAX]; Generator *dataGenerator; public slots: void dataAvailable(int val); signals: void runGenerator(bool run); }; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { QGridLayout *layout = new QGridLayout; for(int iCol=0; iCol<COL_MAX; iCol++) { for(int iRow=0; iRow<ROW_MAX; iRow++) { QLabel *label = new QLabel("Hello"); labels[iRow][iCol] = label; layout->addWidget(labels[iRow][iCol], iRow, iCol); } } centralWidget->setLayout(layout); dataGenerator = new Generator; QThread *dgThread = new QThread; dataGenerator->moveToThread(dgThread); dgThread->start(QThread::HighestPriority); connect(this, SIGNAL(runGenerator(bool)), dataGenerator, SLOT(run(bool))); connect(dataGenerator, SIGNAL(dataAvailable(int)), this, SLOT(dataAvailable(int))); emit runGenerator(true); } void MainWindow::dataAvailable(int val) { for(int iCol=0; iCol< COL_MAX; iCol++) { for(int iRow=0; iRow<ROW_MAX; iRow++) { labels[iRow][iCol]->setText(QString::number(val)); } } } ```