QProgressDialog not displaying the first time
-
Hello,
In my GUI at some point, I want to display a progress bar while I do some time-consuming computations. For this, I want to display a QProgressDialog and connect its setValue(int) function to a signal that I will send from the function that does the computations. It is something similar to what you can find in this cmake project. However, It does not behave as expected. In this example, I create a pushbutton and a spin box. Then once the push button is pressed the QProgressDialog should appear and display the progress of a for loop in another function. Instead, it does nothing the first time and does the expected from the second onwards. Moreover, if iteration is set to 5000 then after a while some more QProgressDialog windows appear (exactly as many as the times Go! button has been pressed). Any ideas on how to solve this issues? The one that does not appear on the first time is the most frustrating one to me.
-
Thank you very much for all your efforts. Now I have it working fine. For future googling here is the whole project. The final code is:
Worker Header#ifndef THREADEDWORKER_H #define THREADEDWORKER_H #include <unistd.h> #include <atomic> #include <QtCore> #include <QObject> #include <QThread> class ThreadedWorker : public QObject { Q_OBJECT public: explicit ThreadedWorker(int n_iterations, QObject *parent = 0); signals: void status(int i); void finished(); void finished(ThreadedWorker* worker); public slots: void process(); void quit(); protected: std::atomic_bool isRunning_; int n_iterations; }; #endif // THREADEDWORKER_H
Worker source
#include "threadedworker.h" ThreadedWorker::ThreadedWorker(int n_it, QObject *parent) : QObject(parent),n_iterations(n_it) { isRunning_ = false; } void ThreadedWorker::process() { if(!this->isRunning_) { isRunning_ = true; for (int i=0;i<n_iterations && isRunning_;i++){ emit this->status(i); qDebug()<<QThread::currentThreadId()<<": "<<i; usleep(20000); } emit this->finished(this); // added to inform a controller object that the worker has finished emit this->finished(); } } void ThreadedWorker::quit(){ isRunning_ = false; }
Main window header
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QtCore> #include <QMainWindow> #include <QProgressDialog> #include <QObject> #include <QThread> #include "threadedworker.h" namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private slots: void on_pushButton_nowhait_clicked(); void on_pushButton_whait_clicked(); private: Ui::MainWindow *ui; QProgressDialog* progress; }; #endif // MAINWINDOW_H
Main window soruce
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); progress = new QProgressDialog("Iterating...","Cancell", 0, ui->spinBox->value(), this); } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_pushButton_nowhait_clicked() { qDebug()<<"No whait push button clicked"; // Run in new thread and create a progress bar progress->setMinimum(0); progress->setMaximum(ui->spinBox->value()); progress->setWindowModality(Qt::WindowModal); progress->setAutoClose(true); progress->show(); QThread* th = new QThread; ThreadedWorker* work = new ThreadedWorker(ui->spinBox->value()); work->moveToThread(th); connect(th, SIGNAL(started()), work, SLOT(process())); connect(work,SIGNAL(status(int)),progress,SLOT(setValue(int))); connect(work, SIGNAL(finished()), th, SLOT(quit())); connect(work,SIGNAL(finished()),progress,SLOT(close())); connect(work, SIGNAL(finished()), work, SLOT(deleteLater())); connect(th, SIGNAL(finished()), th, SLOT(deleteLater())); connect(progress,SIGNAL(canceled()),work,SLOT(quit()),Qt::DirectConnection); th->start(); qDebug()<<"exit no whait push button callback"; } void MainWindow::on_pushButton_whait_clicked() { qDebug()<<"Whait push button clicked"; // Run in new thread and create a progress bar progress->setMinimum(0); progress->setMaximum(ui->spinBox->value()); progress->setWindowModality(Qt::WindowModal); progress->setAutoClose(true); progress->show(); ThreadedWorker* work = new ThreadedWorker(ui->spinBox->value()); connect(work,SIGNAL(status(int)),progress,SLOT(setValue(int))); connect(work,SIGNAL(finished()),progress,SLOT(close())); connect(work,SIGNAL(finished()),work,SLOT(deleteLater())); connect(progress,SIGNAL(canceled()),work,SLOT(quit()),Qt::DirectConnection); work->process(); qDebug()<<"exit whait push button callback"; }
-
- in
MainWindow::on_pushButton_clicked
,th.waitForFinished();
blocks the main event loop (so it's like not having a separate thread) - Qt::Concurrent is abused here. The second thread will call the signal of the object living in the main thread potentially race conditioning it. see http://doc.qt.io/qt-5/threads-technologies.html for choosing the appropriate tool
Basically the logic behind that example is pretty flawed
- in
-
Thank you @VRonin for your answer. After reading and trying to understand some of the documentation I have come to a solution. I have realised that probably the best way to do such work is using a worker thread. For this, I have done the following:
HEADER FILE#include <unistd.h> #include <QtCore> #include <QMainWindow> #include <QProgressDialog> #include <QObject> #include <QThread> class ThreadedWorker : public QObject { Q_OBJECT public: explicit ThreadedWorker(int n_iterations, QObject *parent = 0); signals: void status(int i); void finished(); void finished(ThreadedWorker* worker); public slots: void start(); protected: virtual void run(); QThread workerThread_; bool isRunning_; int n_iterations; }; namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); signals: void newIteration(int i); private slots: void on_pushButton_clicked(); private: Ui::MainWindow *ui; QProgressDialog* progress; };
SOURCE FILE
#include "mainwindow.h" #include "ui_mainwindow.h" ThreadedWorker::ThreadedWorker(int n_it, QObject *parent) : QObject(parent),n_iterations(n_it) { this->moveToThread(&this->workerThread_); this->workerThread_.start(); this->isRunning_ = false; } void ThreadedWorker::start() { if(!this->isRunning_) { this->isRunning_ = true; this->run(); } } void ThreadedWorker::run() { for (int i=0;i<n_iterations;i++){ emit this->status(i); qDebug()<<QThread::currentThreadId()<<": "<<i; usleep(2000); } emit this->finished(this); // added to inform a controller object that the worker has finished emit this->finished(); } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); progress = new QProgressDialog("Iterating...", "", 0, ui->spinBox->value(), this); } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_pushButton_clicked() { qDebug()<<"push button clicked"; // Run in new thread and create a progress bar progress->setMinimum(0); progress->setMaximum(ui->spinBox->value()); progress->setWindowModality(Qt::WindowModal); progress->setAutoClose(true); progress->setCancelButton(0); progress->show(); ThreadedWorker* work = new ThreadedWorker(ui->spinBox->value()); connect(work,SIGNAL(status(int)),progress,SLOT(setValue(int))); connect(work,SIGNAL(finished()),progress,SLOT(close())); work->start(); qDebug()<<"exit push button callback"; }
Is this closer to how it is supposed to be done in QT?
-
Very close! see https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/
P.S.
You can't have 2 Q_OBJECT in the same header file (You probably know it already but just a note for people googling this in the future) -
Thank you @VRonin, I think I got it now. I've rewritten some things so it is possible to see why the previous code was "wrong" (and I say it between " because depending on what you want it might not be wrong) I guess that the previous code was wrong because it is missing the part where I move the worker to a thread, am I right? The difference is that as I did it the pushButton function does not quit until the worker is done, while if I move it to a thread then the callback ends before the work is done. The fact that this (the callback finishing before the work is done) might not be interesting if you are doing something on a QCloseEvent. In any case, for people googling this in the future, I attach the code. Now it is split into two files (one for each object) and the ui has two buttons, one that calls the working thread as before (pushButton_whait) and the one that is implemented properly (pusButton_nowhait).
Worker header
#ifndef THREADEDWORKER_H #define THREADEDWORKER_H #include <unistd.h> #include <QtCore> #include <QObject> #include <QThread> class ThreadedWorker : public QObject { Q_OBJECT public: explicit ThreadedWorker(int n_iterations, QObject *parent = 0); signals: void status(int i); void finished(); void finished(ThreadedWorker* worker); public slots: void process(); void quit(); protected: int n_iterations; }; #endif // THREADEDWORKER_H
Worcer source
#include "threadedworker.h" ThreadedWorker::ThreadedWorker(int n_it, QObject *parent) : QObject(parent),n_iterations(n_it) { } void ThreadedWorker::process() { for (int i=0;i<n_iterations;i++){ emit this->status(i); qDebug()<<QThread::currentThreadId()<<": "<<i; usleep(200000); } emit this->finished(this); // added to inform a controller object that the worker has finished emit this->finished(); } void ThreadedWorker::quit(){ qDebug()<<"How do I make it stop!?"; thread()->exit();// it does not stop the execution! }
Main window header
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QtCore> #include <QMainWindow> #include <QProgressDialog> #include <QObject> #include <QThread> #include "threadedworker.h" namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); signals: void newIteration(int i); private slots: void on_pushButton_nowhait_clicked(); void on_pushButton_whait_clicked(); private: Ui::MainWindow *ui; QProgressDialog* progress; }; #endif // MAINWINDOW_H
Main window source
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); progress = new QProgressDialog("Iterating...","Cancell", 0, ui->spinBox->value(), this); } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_pushButton_nowhait_clicked() { qDebug()<<"No whait push button clicked"; // Run in new thread and create a progress bar progress->setMinimum(0); progress->setMaximum(ui->spinBox->value()); progress->setWindowModality(Qt::WindowModal); progress->setAutoClose(true); progress->show(); QThread* th = new QThread; ThreadedWorker* work = new ThreadedWorker(ui->spinBox->value()); work->moveToThread(th); connect(th, SIGNAL(started()), work, SLOT(process())); connect(work,SIGNAL(status(int)),progress,SLOT(setValue(int))); connect(work, SIGNAL(finished()), th, SLOT(quit())); connect(work,SIGNAL(finished()),progress,SLOT(close())); connect(work, SIGNAL(finished()), work, SLOT(deleteLater())); connect(th, SIGNAL(finished()), th, SLOT(deleteLater())); connect(progress,SIGNAL(canceled()),th,SLOT(quit())); connect(progress,SIGNAL(canceled()),work,SLOT(quit())); th->start(); qDebug()<<"exit no whait push button callback"; } void MainWindow::on_pushButton_whait_clicked() { qDebug()<<"Whait push button clicked"; // Run in new thread and create a progress bar progress->setMinimum(0); progress->setMaximum(ui->spinBox->value()); progress->setWindowModality(Qt::WindowModal); progress->setAutoClose(true); progress->show(); ThreadedWorker* work = new ThreadedWorker(ui->spinBox->value()); connect(work,SIGNAL(status(int)),progress,SLOT(setValue(int))); connect(work,SIGNAL(finished()),progress,SLOT(close())); connect(progress,SIGNAL(canceled()),work,SLOT(quit())); work->process(); qDebug()<<"exit whait push button callback"; }
As you can see I have also tryied to implement the option to cancell the process. However, I was not sucessfull. When I start the worker using the whait button the event is not even processd (which makes sence because the main thread is stuck whaiting for the worker to finish), and when I call it from the non whaitting button, the signal is processed but the tread does not stops. Any Idea?
The package ready to be downloaded and compiled is here (I use cmake not qmake, sorry if it is an inconvenience to whoever downloads it).
-
Probably just because from where I "took the idea" (a.k.a. copied it XD), I have removed it from the worker class. Does somebody have any idea on why I can not make it stop as I point out at the end of my previous post??
-
add an atomic boolean guard
class ThreadedWorker : public QObject { Q_OBJECT public: explicit ThreadedWorker(int n_iterations, QObject *parent = 0); signals: void status(int i); void finished(); void finished(ThreadedWorker* worker); public slots: void process(); void quit(); protected: std::atomic_bool m_isRunning; int n_iterations; };
ThreadedWorker::ThreadedWorker(int n_it, QObject *parent) : QObject(parent),n_iterations(n_it) { m_isRunning =false; } void ThreadedWorker::process() { m_isRunning =true; for (int i=0;i<n_iterations && m_isRunning;i++){ emit this->status(i); qDebug()<<QThread::currentThreadId()<<": "<<i; usleep(200000); } emit this->finished(this); // added to inform a controller object that the worker has finished emit this->finished(); } void ThreadedWorker::quit(){ m_isRunning =false; }
now you can direct connect to
ThreadedWorker::quit
to stop.
i.e. replaceconnect(progress,SIGNAL(canceled()),th,SLOT(quit())); connect(progress,SIGNAL(canceled()),work,SLOT(quit()));
with
connect(progress,&QProgressDialog::canceled,work,&ThreadedWorker::quit,Qt::DirectConnection);
-
Thank you very much for all your efforts. Now I have it working fine. For future googling here is the whole project. The final code is:
Worker Header#ifndef THREADEDWORKER_H #define THREADEDWORKER_H #include <unistd.h> #include <atomic> #include <QtCore> #include <QObject> #include <QThread> class ThreadedWorker : public QObject { Q_OBJECT public: explicit ThreadedWorker(int n_iterations, QObject *parent = 0); signals: void status(int i); void finished(); void finished(ThreadedWorker* worker); public slots: void process(); void quit(); protected: std::atomic_bool isRunning_; int n_iterations; }; #endif // THREADEDWORKER_H
Worker source
#include "threadedworker.h" ThreadedWorker::ThreadedWorker(int n_it, QObject *parent) : QObject(parent),n_iterations(n_it) { isRunning_ = false; } void ThreadedWorker::process() { if(!this->isRunning_) { isRunning_ = true; for (int i=0;i<n_iterations && isRunning_;i++){ emit this->status(i); qDebug()<<QThread::currentThreadId()<<": "<<i; usleep(20000); } emit this->finished(this); // added to inform a controller object that the worker has finished emit this->finished(); } } void ThreadedWorker::quit(){ isRunning_ = false; }
Main window header
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QtCore> #include <QMainWindow> #include <QProgressDialog> #include <QObject> #include <QThread> #include "threadedworker.h" namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private slots: void on_pushButton_nowhait_clicked(); void on_pushButton_whait_clicked(); private: Ui::MainWindow *ui; QProgressDialog* progress; }; #endif // MAINWINDOW_H
Main window soruce
#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); progress = new QProgressDialog("Iterating...","Cancell", 0, ui->spinBox->value(), this); } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_pushButton_nowhait_clicked() { qDebug()<<"No whait push button clicked"; // Run in new thread and create a progress bar progress->setMinimum(0); progress->setMaximum(ui->spinBox->value()); progress->setWindowModality(Qt::WindowModal); progress->setAutoClose(true); progress->show(); QThread* th = new QThread; ThreadedWorker* work = new ThreadedWorker(ui->spinBox->value()); work->moveToThread(th); connect(th, SIGNAL(started()), work, SLOT(process())); connect(work,SIGNAL(status(int)),progress,SLOT(setValue(int))); connect(work, SIGNAL(finished()), th, SLOT(quit())); connect(work,SIGNAL(finished()),progress,SLOT(close())); connect(work, SIGNAL(finished()), work, SLOT(deleteLater())); connect(th, SIGNAL(finished()), th, SLOT(deleteLater())); connect(progress,SIGNAL(canceled()),work,SLOT(quit()),Qt::DirectConnection); th->start(); qDebug()<<"exit no whait push button callback"; } void MainWindow::on_pushButton_whait_clicked() { qDebug()<<"Whait push button clicked"; // Run in new thread and create a progress bar progress->setMinimum(0); progress->setMaximum(ui->spinBox->value()); progress->setWindowModality(Qt::WindowModal); progress->setAutoClose(true); progress->show(); ThreadedWorker* work = new ThreadedWorker(ui->spinBox->value()); connect(work,SIGNAL(status(int)),progress,SLOT(setValue(int))); connect(work,SIGNAL(finished()),progress,SLOT(close())); connect(work,SIGNAL(finished()),work,SLOT(deleteLater())); connect(progress,SIGNAL(canceled()),work,SLOT(quit()),Qt::DirectConnection); work->process(); qDebug()<<"exit whait push button callback"; }