如何使用 QProgressDialog 的取消按钮停止/取消工作作业 [英] How to stop/cancel a worker job using the cancel button of a QProgressDialog

查看:123
本文介绍了如何使用 QProgressDialog 的取消按钮停止/取消工作作业的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的代码由一个工作类和一个对话框类组成.worker 类启动一个工作(一个很长的工作).我的对话框类有 2 个按钮,允许启动和停止作业(它们工作正常).我想实现一个忙碌的酒吧,显示工作正在进行中.我在 Worker 类中使用了 QProgressDialog.当我想使用 Qpr​​ogressDialog cancel 按钮停止作业时,我无法捕捉信号 &QProgressDialog::canceled.我试过了,这个(放在 Worker 构造函数中):

My code is composed of a worker class and a dialog class. The worker class launches a job (a very long job). My dialog class has 2 buttons that allows for launching and stopping the job (they work correctly). I would like to implement a busy bar showing that a job is underway. I have used a QProgressDialog in the Worker class. When I would like to stop the job using the QprogressDialog cancel button, I can't catch the signal &QProgressDialog::canceled. I tried, this (put in the Worker constructor):

QObject::connect(progress, &QProgressDialog::canceled, this, &Worker::stopWork);

没有任何影响.

您可以在下面看到完整的编译代码.

You can see the complete compiling code below.

如何通过单击 QprogressDialog 取消按钮来停止作业?

How I can stop the job by clicking on the QprogressDialog cancel button?

以下是我在必要时重现行为的完整代码.

Here below is my complete code to reproduce the behavior if necessary.

//worker.h

#ifndef WORKER_H
#define WORKER_H
#include <QObject>
#include <QProgressDialog>
class Worker : public QObject
{
    Q_OBJECT
public:
    explicit Worker(QObject *parent = nullptr);
    virtual ~Worker();
    QProgressDialog * getProgress() const;
    void setProgress(QProgressDialog *value);
signals:
    void sigAnnuler(bool);
    // pour dire que le travail est fini
    void sigFinished();
    // mise à jour du progression bar
    void sigChangeValue(int);
public slots:
    void doWork();
    void stopWork();
private:
    bool workStopped = false;
    QProgressDialog* progress = nullptr;
};
#endif // WORKER_H

//worker.cpp

// worker.cpp

#include "worker.h"
#include <QtConcurrent>
#include <QThread>
#include <functional>
// Worker.cpp
Worker::Worker(QObject* parent/*=nullptr*/)
{
    //progress = new QProgressDialog("Test", "Test", 0, 0);
    QProgressDialog* progress = new QProgressDialog("do Work", "Annuler", 0, 0);
    progress->setMinimumDuration(0);
    QObject::connect(this, &Worker::sigChangeValue, progress, &QProgressDialog::setValue);
    QObject::connect(this, &Worker::sigFinished, progress, &QProgressDialog::close);
    QObject::connect(this, &Worker::sigAnnuler, progress, &QProgressDialog::cancel);
    QObject::connect(progress, &QProgressDialog::canceled, this, &Worker::stopWork);
}
Worker::~Worker()
{
    //delete timer;
    delete progress;
}
void Worker::doWork()
{
    emit sigChangeValue(0);

    for (int i=0; i< 100; i++)
    {

       qDebug()<<"work " << i;
       emit sigChangeValue(0);
       QThread::msleep(100);

       if (workStopped)
       {
           qDebug()<< "Cancel work";
           break;
       }
       
    }
    emit sigFinished();
}
void Worker::stopWork()
{
    workStopped = true;
}
QProgressDialog *Worker::getProgress() const
{
    return progress;
}
void Worker::setProgress(QProgressDialog *value)
{
    progress = value;
}

//mydialog.h

// mydialog.h

#ifndef MYDIALOG_H
#define MYDIALOG_H

#include <QDialog>
#include "worker.h"

namespace Ui {
class MyDialog;
}

class MyDialog : public QDialog
{
    Q_OBJECT

public:
    explicit MyDialog(QWidget *parent = 0);
    ~MyDialog();
    void triggerWork();
    void StopWork();
private:
    Ui::MyDialog *ui;
    QThread* m_ThreadWorker = nullptr;
    Worker* m_TraitementProdCartoWrkr = nullptr;
};

#endif // MYDIALOG_H

#include "mydialog.h"
#include "ui_mydialog.h"
#include <QProgressDialog>
#include <QThread>

MyDialog::MyDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::MyDialog)
{
    ui->setupUi(this);
    m_TraitementProdCartoWrkr = new Worker(this);
    connect(ui->OK, &QPushButton::clicked, this, &MyDialog::triggerWork);
    connect(ui->Cancel, &QPushButton::clicked, this, &MyDialog::StopWork);
}
MyDialog::~MyDialog()
{
    delete ui;
}
void MyDialog::triggerWork()
{
    m_ThreadWorker = new QThread;
    QProgressDialog* progress = m_TraitementProdCartoWrkr->getProgress();
    m_TraitementProdCartoWrkr->moveToThread(m_ThreadWorker);
    QObject::connect(m_ThreadWorker, &QThread::started, m_TraitementProdCartoWrkr, &Worker::doWork);
    m_ThreadWorker->start();
}

void MyDialog::StopWork()
{
    m_TraitementProdCartoWrkr->stopWork();
}

//main.cpp

#include "mydialog.h"
#include "ui_mydialog.h"
#include <QProgressDialog>
#include <QThread>

MyDialog::MyDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::MyDialog)
{
    ui->setupUi(this);
    m_TraitementProdCartoWrkr = new Worker(this);
    connect(ui->OK, &QPushButton::clicked, this, &MyDialog::triggerWork);
    connect(ui->Cancel, &QPushButton::clicked, this, &MyDialog::StopWork);
}

MyDialog::~MyDialog()
{
    delete ui;
}

void MyDialog::triggerWork()
{
    m_ThreadWorker = new QThread;

    QProgressDialog* progress = m_TraitementProdCartoWrkr->getProgress();

    m_TraitementProdCartoWrkr->moveToThread(m_ThreadWorker);
    QObject::connect(m_ThreadWorker, &QThread::started, m_TraitementProdCartoWrkr, &Worker::doWork);
    //QObject::connect(m_ThreadWorker, &QThread::started, progress, &QProgressDialog::exec);

    //QObject::connect(progress, &QProgressDialog::canceled, m_TraitementProdCartoWrkr, &Worker::sigAnnuler);

    m_ThreadWorker->start();
}

void MyDialog::StopWork()
{
    m_TraitementProdCartoWrkr->stopWork();
}

推荐答案

您发送到工作线程的任何信号都将排队,因此在所有工作都已完成后,信号将被处理得太晚.

Any signals you send to the worker thread will be queued, so the signal will be processed too late, after all the work has already been done.

有(至少)三种方法可以避免这个问题:

There is (at least) three ways to avoid this problem:

  1. 在工作时,以常规方式中断您的工作,以便处理传入的信号.例如,您可以使用 QTimer::singleShot(0, ...) 来通知自己何时应该恢复工作.在任何取消/停止工作信号之后,此信号将位于队列的末尾.显然,这会造成破坏并使您的代码复杂化.

  1. While doing the work, in a regular fashion, interrupt your work so incoming signals can be processed. For example, you could use QTimer::singleShot(0, ...) to signal yourself when work should be resumed. This signal will then be at the end of the queue, after any canceled/stop work signals. Obviously this is disruptive and complicates your code.

使用从 GUI 线程设置但从工作线程读取的状态变量.因此,默认为 false 的 bool isCancelled.一旦属实,就停止工作.

Use a state variable that you set from the GUI thread, but read from the worker thread. So, a bool isCancelled that defaults to false. As soon as it is true, stop the work.

有一个控制器对象来管理工人/作业并使用锁定.这个对象提供了一个 isCancelled() 方法,可以被 worker 直接调用.

Have a controller object that manages the worker / jobs and uses locking. This object provides an isCancelled() method to be called directly by worker.

我之前使用了第二种方法,现在在我的代码中使用第三种方法,并且通常将它与进度更新结合起来.每当我发布进度更新时,我还会检查已取消的标志.原因是我对我的进度更新进行计时,以便它们对用户来说是平滑的,但不会彻底阻止工作人员进行工作.

I previously used the second approach, nowadays use the third approach in my code and typically combine it with the progress updates. Whenever I issue a progress update, I also check for canceled flag. The reasoning is that I time my progress updates so that they are smooth to the user, but not exhaustively holding of the worker from doing work.

对于第二种方法,在您的情况下,m_TraitementProdCartoWrkr 将有一个您直接调用的 cancel() 方法(不是通过信号/插槽),因此它将在调用者的线程中运行,并设置取消标志(您可以抛出 std::atomic 混合).GUI/worker 之间的其余通信仍将使用信号 &槽——因此它们在各自的线程中进行处理.

For the second approach, in your case, m_TraitementProdCartoWrkr would have a cancel() method that you call directly (not through signal/slot), so it will run in caller's thread, and set the canceled flag (you may throw std::atomic into the mix). The rest of the communication between GUI/worker would still use signals & slots -- so they are processed in their respective threads.

有关第三种方法的示例,请参阅 在这里这里.作业注册表还管理进度(参见此处),并进一步向监视器发出信号(即进度条).

For an example for the third approach, see here and here. The job registry also manages progress (see here), and signals it further to monitors (i.e., progress bars).

这篇关于如何使用 QProgressDialog 的取消按钮停止/取消工作作业的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆