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.
  1. MainWindow
  2. Frozen Window
  3. MainWindow threading
  4. Progress bar
  5. Progress control
  6. Suspend/Resume a thread

  1. To Top

    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();
    }
    
  2. To Top

    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 )
  3. To Top

    MainWindow threading

    Simply to make the number ticking, a separated tread is needed. Let's make one.
    1. 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;
      };
      
    2. 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;
        }
      }
      
    3. 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
        }
      }
      
    4. 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 )
  4. To Top

    Progress bar

    You may not satisfy ticking the number, a normal GUI application uses progress bar. Let's make one.
    1. Declear a progressBar, initialize it, and add clearnup.
      class QProgressBar;
        QProgressBar *progressBar;
      ...
      , progressBar(NULL)
      ...
        if (progressBar) {
          delete progressBar;
          progressBar = NULL;
        }
      
    2. Prepare a thread:
      bool MainWindow::prepareThread(MainWindowThread *&thread)
      {
      ...
        if (jobNumber == 2) {
          if (!progressBar) progressBar = new QProgressBar;
          progressBar->show();
        }
      ...
      }
      
    3. 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);
        }
      }
      
    4. 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 )
  5. To Top

    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.
    1. The control class:
      class ProgressCtrl : public QDialog  
      {
        Q_OBJECT
      public:
        ProgressCtrl(QWidget *parent=0);
        virtual ~ProgressCtrl();
      
      private:
        QPushButton *suspendResumeButton, *stopButton;
        QLabel *nameLabel;
        QProgressBar *progressBar;
      };
      
    2. The connection:
          connect(progressCtrl->stop(), SIGNAL(clicked()), this, SLOT(setStop()));
      
    3. The action upon Stop Button click:
      void MainWindow::setStop()
      {
        m_stop = true;
        if (progressCtrl) progressCtrl->hide();
      }
      
    4. 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 )
  6. To Top

    Suspend/Resume a thread

    Want suspend/resume a thread? Here they are.
    1. Declear a waitCondition, and mutex
        QWaitCondition *waitCondition; QMutex *mutex;
      
    2. Connection when the thead is started
          connect(progressCtrl->suspendResume(), SIGNAL(clicked()), 
            this, SLOT(setSuspendResume()));
      
    3. 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++