I created a minimal QT GUI example to update widgets from a worker thread based on the recommended approach in the The QThread 5.12 documentation.
As described in the QThread 5.12 documentation, the Worker class (with a potentially long void doWork(const QString ¶meter) method is:
class Worker : public QObject
{
Q_OBJECT
public slots:
void doWork(const QString ¶meter) {
QString result;
/* ... here is the expensive or blocking operation ... */
emit resultReady(result);
}
signals:
void resultReady(const QString &result);
};
and the corresponding Controller class is:
class Controller : public QObject
{
Q_OBJECT
QThread workerThread;
public:
Controller() {
Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(this, &Controller::operate, worker, &Worker::doWork);
connect(worker, &Worker::resultReady, this, &Controller::handleResults);
workerThread.start();
}
~Controller() {
workerThread.quit();
workerThread.wait();
}
public slots:
void handleResults(const QString &);
signals:
void operate(const QString &);
};
Unlike sub-classing from a QThread
, the approach shown in the documentation shows the recommended way that uses a controller and a worker that extends QObject
rather than extending QThread and overriding the QThread::run
method, however it does not show how these should be used in the context of a real example.
I need to use an QT Worker thread that updates widgets on a GUI using a timer.
I also need to be able to halt and restart/relaunch this thread with different parameters and I am having some trouble with how to do this correctly. indicates the preferred way to do this via a Controller and a Worker but the connect logic is a bit confusing.
The place where I need help is how to properly integrate the timer in my worker thread and also how to stop and restart a replacement worker when the current one has either finished or been interrupted and restarted.
My working code is made up of the following files.
Controller.h
#pragma once
// SYSTEM INCLUDES
#include <QObject>
#include <QThread>
// APPLICATION INCLUDES
#include "Worker.h"
// DEFINES
// EXTERNAL FUNCTIONS
// EXTERNAL VARIABLES
// CONSTANTS
// STRUCTS
// TYPEDEFS
// FORWARD DECLARATIONS
class Controller : public QObject
{
Q_OBJECT
QThread workerThread;
public:
Controller(/*MainWindow* mainWindow*/) {
auto worker = new Worker;
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(this, &Controller::operate, worker, &Worker::doWork);
connect(worker, &Worker::resultReady, this, &Controller::handleResults);
workerThread.start();
}
~Controller() {
workerThread.quit();
workerThread.wait();
}
public slots:
void handleResults(const QString &) {
// how do I update the mainWindow from here
}
signals:
void operate(int);
};
Worker.h
#pragma once
// SYSTEM INCLUDES
#include <QTimer>
#include <QObject>
#include <QEventLoop>
// APPLICATION INCLUDES
#include "Worker.h"
// DEFINES
// EXTERNAL FUNCTIONS
// EXTERNAL VARIABLES
// CONSTANTS
// STRUCTS
// TYPEDEFS
// FORWARD DECLARATIONS
class Worker : public QObject
{
Q_OBJECT
public slots:
void doWork(int count) {
QString result = "finished";
// Event loop allocated in workerThread
// (non-main) thread affinity (as moveToThread)
// this is important as otherwise it would occur
// on the main thread.
QEventLoop loop;
for (auto i=0; i< count; i++) {
// wait 1000 ms doing nothing...
QTimer::singleShot(1000, &loop, SLOT(quit()));
// process any signals emitted above
loop.exec();
emit progressUpdate(i);
}
emit resultReady(result);
}
signals:
void progressUpdate(int secondsLeft);
void resultReady(const QString &result);
};
MainWindow.h - I needed to add a Controller
member here. I also added an updateValue slot here where I wish to update the GUI. Unfortunately I don't know how to get the controller or the worker to connect
a signal from the thread to update this slot.
#pragma once
// SYSTEM INCLUDES
#include <memory>
#include <QMainWindow>
// APPLICATION INCLUDES
// DEFINES
// EXTERNAL FUNCTIONS
// EXTERNAL VARIABLES
// CONSTANTS
// STRUCTS
// TYPEDEFS
// FORWARD DECLARATIONS
namespace Ui {
class MainWindow;
}
class Controller;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_pushButton_clicked();
void updateValue(int secsLeft);
private:
Ui::MainWindow *ui;
std::unique_ptr<Controller> mpController;
};
MainWindow.cpp -
#include <QThread>
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include "Controller.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, mpController(std::make_unique<Controller>())
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
emit mpController->operate(100);
}
void MainWindow::updateValue(int secsLeft)
{
ui->secondsLeft->setText(QString::number(secsLeft));
}
and finally main.cpp
#include "MainWindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
I basically need help and an explanation on how I should use the QT Thread's controller/worker integrated in my GUI.