Satyam Raikar
Satyam Raikar

Reputation: 473

Show Qt dialog asynchronously

I want to show QT dialog as soon as show() is called; without waiting for the function to end.

void SomeFunction()
{
dialog_.reset(new MessageBoxProgression(this, SLOT(cancel()));
dialog_->show();// not displayed waits for longOperation() to finish
longOperation();
}

dialog_ is having a progress bar which needs to be shown and updated asynchronously, but currently, dialog_ is not show until longOperation() has finished executing.

EDIT: Can this be done?

void SomeFunction()
{
dialog_.reset(new MessageBoxProgression(this, SLOT(cancel()));
dialog_->show();// not displayed waits for longOperation() to finish

QApplication::processEvents();
longOperation();

update(dialog_);
QApplication::processEvents();

longOperation2();
}

Upvotes: 0

Views: 1164

Answers (2)

The main thread should not be doing anything besides gui work. Long-running operations do not belong in the main thread at all. If you have a long-running operation, you should execute it asynchronously.

We can factor out some common long-operation features:

class LongOperationBase : public QObject {
  Q_OBJECT
  std::atomic_bool stop, running;
protected:
  bool shouldRun() const { return !stop; }
  virtual void compute() = 0;
public:
  Q_SLOT void start() {
    stop = false;
    emit started();
    QtConcurrent::run([this]{
      if (running || stop) return;
      running = true;
      compute();
      running = false;
    });
  }
  LongOperationBase() {}
  LongOperationBase(QProgressDialog *pd) {
    connectTo(pd);
  }
  bool isRunning() const { return running; }
  Q_SLOT void cancel() { stop = true; } // thread-safe
  Q_SIGNAL void started();
  Q_SIGNAL void hasRange(int);
  Q_SIGNAL void hasProgress(int);
  void connectTo(QProgressDialog *pd) {
    using B = LongOperationBase;
    connect(this, &B::started, pd, &QProgressDialog::show);
    connect(this, &B::hasRange, pd, &QProgressDialog::setMaximum);
    connect(this, &B::hasProgress, pd, &QProgressDialog::setValue);
    connect(pd, &QProgressDialog::canceled, this, &B::cancel, Qt::DirectConnection);
  }
};

Suppose that you have the following long operation - the input and output data types are given only as an example. Each loop iteration should take 1-10ms to minimize the overhead of checking the status and emitting progress. You can easily perform these checks every M iterations.

struct LongOperation final : LongOperationBase {
  std::vector<int> input;
  std::vector<double> output;
  using LongOperationBase::LongOperationBase;
  void compute() override {
    size_t const N = input.size();
    size_t const M = 1;
    emit hasRange(N);
    for (size_t i = 0, j = 0; i < N; ++i, ++j) {
      // operate on input, generate output
      ...
      if (j == (M-1)) {
        emit hasProgress(i);
        if (!shouldRun()) break;
        j = 0;
      }
    }
  }
};

Then, to execute it asynchronously:

class Window : public QWidget {
  Q_OBJECT
  QProgressDialog m_progress{this};
  LongOperation m_operation{&m_progress};
  ...
public:
  Window(QWidget * parent = {}) : QWidget(parent) {}
  void runLongOperation() {
    m_operation.start();
  }
  ...
};

An alternative approach using the QFuture system is given in this answer, but it's not complete enough to streamline the progress indications. The above QObject-based approach is a workable solution in the interim.

Upvotes: 1

Gurushant
Gurushant

Reputation: 942

Just add QApplication::processEvents();

void SomeFunction()
{
    dialog_.reset(new MessageBoxProgression(this, SLOT(cancel()));
    dialog_->show();// not displayed waits for longOperation() to finish
    QApplication::processEvents(); 
    longOperation();
}

this will solve the problem

Upvotes: 1

Related Questions