1

I try to use a QProgressDialog to give the user some information on the progress of a long task, while allowing him to cancel this task.

Basically, I have a QDialog with a button Compute. By clicking on it, a time consuming method is called on a member of my QDialog's parent. This method takes a callback to tell the caller the progress of work.

The problem is that the progress dialog takes some time before appearing, and doesn't take into account immediately a click on its Cancel button.

It's clear that there is a glitch in my code, but I'm not accustomed with Qt, and I tried many things. I probably need a separate thread.

An extract of my code:

void MyDialog::callbackFunction(int value, void * objPtr) {
  ((QProgressDialog *)(objPtr))->setValue(value);
  QCoreApplication::processEvents();
}

void MyDialog::on_mComputeBtn_clicked()
{
   Compute();
}

void MyDialog::Compute()
{
  QProgressDialog progressDialog("Optimization in progress...", "Cancel", 0, 100, this);
  progressDialog.setMinimumDuration(500); // 500 ms
  progressDialog.setWindowModality(Qt::WindowModal);
  progressDialog.setValue(0);
  connect(&progressDialog, SIGNAL(canceled()), this, SLOT(Cancel()));
  QCoreApplication::processEvents();

    parentMember->LongComputation(callbackFunction);

    // probably useless
  progressDialog.reset();
  progressDialog.hide();
  QCoreApplication::processEvents();
}
Greg82
  • 1,000
  • 3
  • 10
  • 24

2 Answers2

4

The dialog is not appearing immediately because you set a minimum duration of 500ms. Set it to 0 to make the dialog show immediately on progress change or call its show function manually.

In order to make the cancel button work, move your long computation to another thread ( e.g. use QThread or std::async ) and let your main event loop continue its execution.

Actually there are a lot of problems with your code but these two points should point you to the right direction. In my experience every manual call to processEvents is a big code smell.

choosyg
  • 774
  • 7
  • 23
  • "In my experience every manual call to processEvents is a big code smell" - I very much agree. Calling `processEvents()` is a clear sign that you are doing something wrong. – Jesper Juhl Feb 12 '18 at 11:05
  • Yes, I felt that manually calling `processEvents` rhymes with poor code, but it was mainly for trying to make it work. At least, `show()` visually solves a part of the problem. I will refactor this whole piece of code. – Greg82 Feb 12 '18 at 11:14
2

What you do is trying to use modal paradigm of QProgressdialog, not letting main event pump to fire, also you set a 0.5 s timeout, minimal duration would ensure the pause. Maybe modeless variant is more appropriate for your case.

if process got discrete steps, you can do it without separate thread

class MyTask : public QObject
{
    Q_OBJECT
public:
    explicit MyTask(QObject *parent = 0);

signals:

public slots:
    void perform();
    void cancel();
private:
    int steps;
    QProgressDialog *pd;
    QTimer *t;

};

...

void MyDialog::on_mComputeBtn_clicked()
{
    myTask = new MyTask;
}
...


MyTask::MyTask(QObject *parent) :
    QObject(parent), steps(0)
{
    pd = new QProgressDialog("Task in progress.", "Cancel", 0, 100000);
    connect(pd, SIGNAL(canceled()), this, SLOT(cancel()));
    t = new QTimer(this);
    connect(t, SIGNAL(timeout()), this, SLOT(perform()));
    t->start(0);
}

void MyTask::perform()
{
    pd->setValue(steps);
    //... perform one percent of the operation
    steps++;
    if (steps > pd->maximum())
        t->stop();
}

void MyTask::cancel()
{
    t->stop();
    //... cleanup
}

QThread example existed here: Qt 5 : update QProgressBar during QThread work via signal

Swift - Friday Pie
  • 12,777
  • 2
  • 19
  • 42
  • Thanks. Unfortunately, the main work is performed in a separate class, and I can't import it as the `MyTask::perform()` method. I can just call it with a callback. So I think I cannot do without a Qthread... – Greg82 Feb 12 '18 at 11:12
  • @Greg82 why, execute it from QThread, use callback in that thread to update progress through signal? how it able to accept your method as a callback by the way? Does it perform actions not allowed in separate thread? – Swift - Friday Pie Feb 12 '18 at 11:39
  • No it doesn't, it's for I said I cannot do without ;) – Greg82 Feb 12 '18 at 12:09