![](/img/trans.png)
[英]Qt/C++ How can I disconnect a QProgressDialog::canceled signal to its QProgressDialog::cancel slot?
[英]How to stop/cancel a worker job using the cancel button of a QProgressDialog
我的代碼由一個工人 class 和一個對話框 class 組成。工人 class 啟動了一個工作(一個很長的工作)。 我的對話框 class 有 2 個按鈕,允許啟動和停止作業(它們工作正常)。 我想實現一個忙碌的酒吧,表明工作正在進行中。 我在 Worker class 中使用了 QProgressDialog。當我想使用 QprogressDialog cancel
按鈕停止作業時,我無法捕捉到信號&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
// 工人.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;
}
// 我的對話框.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();
}
// 主.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();
}
您發送到工作線程的任何信號都將排隊,因此在所有工作已經完成之后,信號將被處理得太晚。
有(至少)三種方法可以避免這個問題:
在進行工作時,以常規方式中斷您的工作,以便可以處理傳入的信號。 例如,您可以使用QTimer::singleShot(0, ...)
來通知自己何時應該恢復工作。 在任何取消/停止工作信號之后,該信號將位於隊列的末尾。 顯然,這是破壞性的,會使您的代碼復雜化。
使用您從 GUI 線程設置但從工作線程讀取的 state 變量。 因此,默認為 false 的bool isCancelled
。 一旦屬實,立即停止工作。
有一個 controller object 管理工人/工作並使用鎖定。 這個object提供了一個isCancelled()
方法,worker可以直接調用。
我以前使用第二種方法,現在在我的代碼中使用第三種方法,並且通常將它與進度更新結合起來。 每當我發布進度更新時,我也會檢查取消標志。 原因是我對我的進度更新進行了計時,以便它們對用戶來說是流暢的,但不會完全阻止工人工作。
對於第二種方法,在您的情況下,m_TraitementProdCartoWrkr 將有一個您直接調用的 cancel() 方法(而不是通過信號/插槽),因此它將在調用者的線程中運行,並設置取消標志(您可以拋出std::atomic
混入)。 GUI/worker 之間的通信 rest 仍將使用信號和槽——因此它們在各自的線程中進行處理。
看看使用高級 QtConcurrent API 重寫代碼有多么容易:
我的對話框.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;
};
我的對話框.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多線程的缺失文章
線程事件QObjects
@ypnos,感謝您的想法。 我為解決問題所做的是修改:
QObject::connect(progress, &QProgressDialog::canceled, this, &Worker::stopWork);
從Worker
構造函數到這一行:
QObject::connect(progress, &QProgressDialog::canceled, [&]() {
this->stopWork();
});
現在我可以通過QProgressDialog
的cancel
按鈕停止作業。
我不明白的是,為什么第一個代碼(下面)不起作用?
QObject::connect(progress, &QProgressDialog::canceled, this, &Worker::stopWork);
它不起作用,因為signals/slots
的連接類型是在發出信號時選擇的,默認情況下為Qt::AutoConnection
,但我在接收器和發射器之間有不同的線程。 (在此處查看更多詳細信息),因此無法正常工作
然后,我必須指定在發出信號時使用哪種類型的連接來立即調用插槽,因此,這段代碼現在也可以工作(主要區別在於,在這里,我們明確指定連接類型Qt::DirectConnection
):
QObject::connect(progress, &QProgressDialog::canceled, this, &Worker::stopWork, Qt::DirectConnection);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.