Second Thread in MainWindow
Lingfa Yang
Application thread (main thread) is the only thread which does Windows update and uses GUI widgets.
If you run a heavy duty job in this thread, there is no time for updating the Windows, so the Windows get frozen.
The solution is do the heavy duty job in a separate thread, and communicate with the mainWindow for progress indication or progress control. Here is HOW-TO-DO.
-
MainWindow
-
Frozen Window
-
MainWindow threading
-
Progress bar
-
Progress control
-
Suspend/Resume a thread
-
MainWindow
Let's start from a MainWindow, which usually has icon, title, minimum-, maximum-, close- buttons, menu, toolbar, mainWindow, status bar...
Step 1: A basic MainWindow.
( zip
| exe
)
Here is the main():
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow mainWin;
mainWin.show();
return app.exec();
}
and here is the window:
MainWindow::MainWindow()
{
textEdit = new QTextEdit;
setCentralWidget(textEdit);
createActions();
createMenus();
createToolBars();
createStatusBar();
readSettings();
}
-
Frozen Window
Here comes a heavy duty job, which takes long time to finish, and because of that, the Window is frozen. setText() supposes to tick the number, but it cann't.
void MainWindow::heavyDuty()
{
for (int i = 0; i < 10; ++ i) {
QTest::qSleep(1000); // mimic a heavy job
textEdit->setText(QString::number(i));
}
}
Step 2: Window is frozen after click "Heavy Duty".
( zip
| exe
)
-
MainWindow threading
Simply to make the number ticking, a separated tread is needed. Let's make one.
-
The class:
#include <QThread>
class MainWindowThread : public QThread
{
Q_OBJECT
public:
MainWindowThread(MainWindow *mainWindow)
: mainWindow(mainWindow){};
virtual ~MainWindowThread(){};
void run() {mainWindow->run();}
protected:
MainWindow *mainWindow;
};
-
When thread starts,
MainWindow::run() will be invoked. If more than one job, here is a place to put them in parallel.
void MainWindow::run()
{
switch(jobNumber) {
case 1:
heavyDutyJob();
break;
case 2:
heavyDutyJob2();
break;
case 3:
heavyDutyJob3();
break;
}
}
-
The job emits a signal.
void MainWindow::heavyDuty()
{
jobNumber = 1;
prepareThread(thisThread);
thisThread->start();
}
void MainWindow::heavyDutyJob()
{
for (int i = 0; i < 10; ++ i) {
emit progress(i);
QTest::qSleep(1000); // mimic a heavy job
}
}
-
The signal is handled in the main GUI thread.
void MainWindow::thisThreadStarted()
{
this->statusBar()->showMessage("Thread is running ...");
connect(this, SIGNAL(progress(int)),
this, SLOT(setProgress(int)), Qt::BlockingQueuedConnection);
}
void MainWindow::setProgress(int pos)
{
textEdit->setText(QString::number(pos));
}
Now, the number ticks.
Step 3: Tick.
( zip
| exe
)
-
Progress bar
You may not satisfy ticking the number, a normal GUI application uses progress bar. Let's make one.
-
Declear a progressBar, initialize it, and add clearnup.
class QProgressBar;
QProgressBar *progressBar;
...
, progressBar(NULL)
...
if (progressBar) {
delete progressBar;
progressBar = NULL;
}
-
Prepare a thread:
bool MainWindow::prepareThread(MainWindowThread *&thread)
{
...
if (jobNumber == 2) {
if (!progressBar) progressBar = new QProgressBar;
progressBar->show();
}
...
}
-
Makde Connection upon thread starts:
void MainWindow::thisThreadStarted()
{
...
if (jobNumber == 2 && progressBar) {
connect(this, SIGNAL(range(int, int)),
progressBar, SLOT(setRange(int, int)), Qt::BlockingQueuedConnection);
connect(this, SIGNAL(progress(int)),
progressBar, SLOT(setValue(int)), Qt::BlockingQueuedConnection);
}
}
-
Disconnect them when thread is finished:
void MainWindow::thisThreadFinished()
{
...
if (jobNumber == 2 && progressBar) {
disconnect(this, SIGNAL(range(int, int)), progressBar, SLOT(setRange(int, int)));
disconnect(this, SIGNAL(progress(int)), progressBar, SLOT(setValue(int)));
progressBar->hide();
progressBar->setValue(0);
}
}
Now, a bar steps.
Step 4: Stepping bar.
( zip
| exe
)
-
Progress control
When a thread is running, you may want to kill it if it takes too long to finish.
This is a simplest control on a thread. Let's make one.
-
The control class:
class ProgressCtrl : public QDialog
{
Q_OBJECT
public:
ProgressCtrl(QWidget *parent=0);
virtual ~ProgressCtrl();
private:
QPushButton *suspendResumeButton, *stopButton;
QLabel *nameLabel;
QProgressBar *progressBar;
};
-
The connection:
connect(progressCtrl->stop(), SIGNAL(clicked()), this, SLOT(setStop()));
-
The action upon Stop Button click:
void MainWindow::setStop()
{
m_stop = true;
if (progressCtrl) progressCtrl->hide();
}
-
Status check inside the job loop
void MainWindow::heavyDutyJob3()
{
emit range(0, 10);
for (int i = 0; i < 10; ++ i) {
emit progress(i);
emit label("Dealing with " + QString::number(i) + " out of 10");
if (m_stop) break;
QTest::qSleep(1000); // mimic a heavy job
}
}
Now, stop-button makes an early end of the thread.
Step 5: Stop button.
( zip
| exe
)
-
Suspend/Resume a thread
Want suspend/resume a thread? Here they are.
-
Declear a waitCondition, and mutex
QWaitCondition *waitCondition; QMutex *mutex;
-
Connection when the thead is started
connect(progressCtrl->suspendResume(), SIGNAL(clicked()),
this, SLOT(setSuspendResume()));
-
Action when the button is clicked (Toggle the status, toggle the button text)
void MainWindow::setSuspendResume()
{
if (!progressCtrl) return;
m_suspended = !m_suspended;
if (m_suspended) progressCtrl->suspendResume()->setText("Resume");
else {
progressCtrl->suspendResume()->setText("Suspend");
waitCondition->wakeOne();
}
}
Now, resume/suspend works.
Step 6: Full version.
( zip
| exe
)
MyQt
| Simple Thread
| C/C++
|