Как реализовать QProgressDialog?

Я пытаюсь использовать QProgressDialog, чтобы предоставить пользователю некоторую информацию о ходе выполнения длинной задачи, позволяя ему отменить эту задачу.

По сути, у меня есть QDialog с кнопкой Compute. При нажатии на него вызывается трудоемкий метод для члена моего родителя QDialog. Этот метод принимает обратный вызов, чтобы сообщить вызывающей стороне о ходе работы.

Проблема в том, что диалоговое окно прогресса занимает некоторое время, прежде чем появится, и не учитывает сразу щелчок по его кнопке Cancel.

Понятно, что в моем коде глюк, но я не освоился с Qt, а много чего перепробовал. Наверное, мне нужна отдельная ветка.

Выдержка из моего кода:

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();
}

person Greg82    schedule 12.02.2018    source источник


Ответы (2)


Диалог не появляется сразу, потому что вы установили минимальную продолжительность 500 мс. Установите его на 0, чтобы диалог отображался сразу при изменении хода выполнения, или вызовите его функцию показа вручную.

Чтобы заставить кнопку отмены работать, переместите свои длинные вычисления в другой поток (например, используйте QThread или std::async ) и дайте вашему основному циклу обработки событий продолжить выполнение.

На самом деле с вашим кодом много проблем, но эти два момента должны указать вам правильное направление. По моему опыту, каждый ручной вызов processEvents — это большой запах кода.

person choosyg    schedule 12.02.2018
comment
По моему опыту, каждый ручной вызов processEvents - это большой запах кода - я полностью согласен. Звонок processEvents() — явный признак того, что вы делаете что-то не так. - person Jesper Juhl; 12.02.2018
comment
Да, я чувствовал, что вызов processEvents вручную рифмуется с плохим кодом, но в основном это было сделано для того, чтобы заставить его работать. По крайней мере, show() визуально решает часть проблемы. Я рефакторинг весь этот кусок кода. - person Greg82; 12.02.2018

Что вы делаете, так это пытаетесь использовать модальную парадигму QProgressdialog, не позволяя главному событию запускаться, также вы устанавливаете тайм-аут 0,5 с, минимальная продолжительность обеспечит паузу. Возможно, для вашего случая больше подходит немодальный вариант.

если процесс имеет дискретные шаги, вы можете сделать это без отдельного потока

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 существовал здесь: Qt 5: обновить QProgressBar во время работы QThread через сигнал

person Swift - Friday Pie    schedule 12.02.2018
comment
Спасибо. К сожалению, основная работа выполняется в отдельном классе, и я не могу импортировать его как метод MyTask::perform(). Я могу просто вызвать его с помощью обратного вызова. Поэтому я думаю, что не могу обойтись без Qthread... - person Greg82; 12.02.2018
comment
@Greg82 Greg82 почему, выполнить его из QThread, использовать обратный вызов в этом потоке, чтобы обновить ход выполнения через сигнал? кстати, как он может принять ваш метод в качестве обратного вызова? Выполняет ли он действия, не разрешенные в отдельном потоке? - person Swift - Friday Pie; 12.02.2018
comment
Нет, это потому, что я сказал, что не могу обойтись без ;) - person Greg82; 12.02.2018