如何使用QProgressDialog的"取消"按钮停止/取消工人作业



我的代码由一个工作者类和一个对话框类组成。工人阶级开始了一项工作(一项很长的工作(。我的对话框类有两个按钮,允许启动和停止作业(它们工作正常(。我想实现一个繁忙的酒吧,显示一项工作正在进行中。我在Worker类中使用了QProgressDialog。当我想使用QprogressDialogcancel按钮停止作业时,我无法捕捉到信号&QProgressDialog::canceled。我试过了,这个(放在Worker构造函数中(:

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

没有任何效果。

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

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

下面是我的完整代码,用于在必要时重现行为。

//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

#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

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

发送给工作线程的任何信号都将被排队,因此在所有工作都完成后,信号处理将太迟。

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

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

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

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

我以前使用第二种方法,现在在代码中使用第三种方法,通常将其与进度更新相结合。每当我发布进度更新时,我也会检查取消的标志。原因是我对进度更新进行计时,这样用户就可以很顺利地进行更新,但不会完全阻止工人工作。

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

有关第三种方法的示例,请参见此处和此处。作业注册表还管理进度(请参阅此处(,并将其进一步发送给监视器(即进度条(。

查看使用High-Level QtConcurrent API:重写代码有多容易

MyDialog.h

#include <QtWidgets/QDialog>
#include "ui_MyDialog.h"
class MyDialog : public QDialog
{
Q_OBJECT
public:
MyDialog(QWidget *parent = nullptr);
~MyDialog();
void triggerWork();
void stopWork();
signals:
void sigChangeValue(int val);
private:
Ui::MyDialogClass ui;
};

MyDialog.cpp

#include "MyDialog.h"
#include <QtConcurrent/QtConcurrent>
#include <QThread>
#include <atomic>
#include <QProgressDialog>
// Thread-safe flag to stop the thread. No mutex protection is needed 
std::atomic<bool> gStop = false;
MyDialog::MyDialog(QWidget *parent)
: QDialog(parent)
{
ui.setupUi(this);
auto progress = new QProgressDialog;
connect(this, &MyDialog::sigChangeValue, 
progress, &QProgressDialog::setValue);
connect(progress, &QProgressDialog::canceled, 
this, [this]()
{
stopWork();
}
);
// To simplify the example, start the work here:
triggerWork();
}
MyDialog::~MyDialog()
{ 
stopWork();
}
void MyDialog::triggerWork()
{
// Run the code in another thread using High-Level QtConcurrent API
QtConcurrent::run([this]()
{
for(int i = 0; i < 100 && !gStop; i++)
{
this->sigChangeValue(i); // signal emition is always thread-safe
qDebug() << "running... i =" << i;
QThread::msleep(100);
}
qDebug() << "stopped";
});
}
void MyDialog::stopWork()
{
gStop = true;
}

另请阅读:

Qt中的线程基础知识
Qt中多线程技术
同步线程
线程和对象
C++中缺少的关于Qt多线程的文章
Threads事件QObjects

@ypnos,我感谢您的想法。我为解决这个问题所做的是修改:

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

Worker构造函数到此行:

QObject::connect(progress, &QProgressDialog::canceled, [&]() {
this->stopWork();
});

现在我可以通过QProgressDialogcancel按钮停止作业。

我不明白的是,为什么第一个代码(下面(不起作用?

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

它不起作用,因为signals/slots的连接类型是在发出信号时选择的,默认情况下是Qt::AutoConnection,但我在接收器和发射器之间有不同的线程。(请参阅此处的更多详细信息(,因此它无法工作

然后,我必须指定在信号发出时使用哪种类型的连接来立即调用插槽,因此,此代码现在也可以工作(主要区别是,在这里,我们明确指定了连接类型Qt::DirectConnection(:

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

相关内容

  • 没有找到相关文章

最新更新