簡體   English   中英

如何使用 QProgressDialog 的取消按鈕停止/取消工作

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

您發送到工作線程的任何信號都將排隊,因此在所有工作已經完成之后,信號將被處理得太晚。

有(至少)三種方法可以避免這個問題:

  1. 在進行工作時,以常規方式中斷您的工作,以便可以處理傳入的信號。 例如,您可以使用QTimer::singleShot(0, ...)來通知自己何時應該恢復工作。 在任何取消/停止工作信號之后,該信號將位於隊列的末尾。 顯然,這是破壞性的,會使您的代碼復雜化。

  2. 使用您從 GUI 線程設置但從工作線程讀取的 state 變量。 因此,默認為 false 的bool isCancelled 一旦屬實,立即停止工作。

  3. 有一個 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();
                                                                 });

現在我可以通過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);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM