Search code examples
c++multithreadingqtloopssignals-slots

Qt - SIGNAL & SLOT are not updating my QLabel in mainwindow from a worker class


I applied an C++ example of working with threads in Qt 5.7. All things are good except two things:

1- I used Signal & Slot to update my label in the main form. the problem is that is no effect. Really, I don't know where is the issue.

2- The loop works fine, but when I exit my program I see (throught the "Application Output") that the loop still work (I think that's related with the started thread).

This my little example:


mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();  

private slots:
    void on_pushButton_clicked();
    void updateLabelText(const QString TheString);

private:
    Ui::MainWindow *ui;

};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "worker.h"

//#include <QDebug>
#include <QThread>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_pushButton_clicked()
{
    QThread *workerThread = new QThread;
    Worker *worker  = new Worker;
    worker->moveToThread(workerThread);
    connect(worker, SIGNAL(sendText(const QString)), this, SLOT(updateLabelText(QString)));
    workerThread->start();
}

void  MainWindow::updateLabelText(const QString TheString)
{
    ui->label->setText(TheString);
}

worker.h

#ifndef WORKER_H
#define WORKER_H

#include <QObject>

class Worker : public QObject
{
    Q_OBJECT

public:
    explicit Worker();

public slots:
    void doWork();

signals:
  void sendText(const QString);

};

#endif // WORKER_H

worker.cpp

#include "worker.h"
#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QDebug>
#include <QThread>
#include <QMessageBox>
#include <QApplication>

Worker::Worker()
{
    doWork();
}

void Worker::doWork()
{
    for (int i = 0; i<999999; i++) {
        emit sendText(QString::number(i));
        qDebug() << "The number is : " + QString::number(i);
        qApp->processEvents();
        //QThread::msleep(5);
    }
}

How can I fix this?

Thanks.


Solution

  • In your code, doWork() function is called from the Worker's constructor, the constructor is invoked in the main thread (that is done in the line Worker* worker= new Worker;).

    Of course, that is not what you meant to do, since it will cause the main thread to execute the for loop in doWork() before even reaching into the connect call.

    Instead of calling doWork() from the Worker's constructor, you should connect the thread's started() signal to doWork() slot, then call thread->start() after moving the Worker object to the new thread. This will leverage Qt cross-thread signals to invoke doWork() in the new thread as soon as it starts.

    Here is how your code should look like:

    #include <QtWidgets>
    
    //QThread wrapper for safe destruction
    //see http://stackoverflow.com/a/19666329
    class Thread : public QThread{
        using QThread::run; //final
    public:
        Thread(QObject* parent= nullptr): QThread(parent){}
        ~Thread(){ quit(); wait();}
    
    };
    
    class Worker : public QObject{
        Q_OBJECT
    public:
        explicit Worker(QObject* parent= nullptr): QObject(parent){}
        ~Worker()= default;
    
        Q_SIGNAL void sendText(QString text);
        Q_SIGNAL void workFinished();
    
        Q_SLOT void doWork(){
            for (int i = 0; i<1000; i++) {
                emit sendText(QString::number(i));
                QThread::msleep(5);
            }
            emit workFinished();
        }
    };
    
    class Widget : public QWidget{
        Q_OBJECT
    public:
        explicit Widget(QWidget* parent= nullptr): QWidget(parent){
            layout.addWidget(&buttonWork);
            layout.addWidget(&label);
    
            connect(&buttonWork, &QPushButton::clicked,
                    this, &Widget::buttonWorkClicked);
    
        }
        ~Widget()= default;
    
        Q_SLOT void buttonWorkClicked(){
            Thread* thread= new Thread(this);
            Worker* worker= new Worker;
            worker->moveToThread(thread);
            //invoke doWork as soon as the thread is started
            connect(thread, &QThread::started, worker, &Worker::doWork);
            connect(worker, &Worker::sendText, this, &Widget::updateLabelText);
            //quit the thread when work is finished
            connect(worker, &Worker::workFinished, thread, &QThread::quit);
            //destroy thread and worker object when work is finished
            connect(thread, &QThread::finished, thread, &QObject::deleteLater);
            connect(thread, &QThread::finished, worker, &QObject::deleteLater);
            //start the thread
            thread->start();
        }
    
        Q_SLOT void updateLabelText(QString text){
            label.setText(text);
        }
    
    private:
        QVBoxLayout layout{this};
        QPushButton buttonWork{"Work"};
        QLabel label{"No work yet"};
    };
    
    int main(int argc, char* argv[]){
        QApplication a(argc, argv);
    
        Widget w;
        w.show();
    
        return a.exec();
    }
    
    #include "main.moc"