【问题标题】:Directing QT Gui from non-main thread从非主线程引导 QT Gui
【发布时间】:2021-05-16 18:19:00
【问题描述】:

我知道以下不是正常的 Qt 模式,但我想从非主线程创建/引导主线程 GUI 元素。

我们的目标是启动一个独立线程,该线程可以长期存在并做自己的事情。我不想在我的主窗口中为子线程可能做的每件可以想象的 GUI 事情添加钩子。我希望子线程能够显示异步通知,例如,在后台处理带有进度条的事情等。

我创建一个工作对象并将其移动到辅助线程。当线程启动时,它会向工作对象发出信号,以实例化QProgressDialog 的包装器,以模拟辅助线程“做自己的事情”。然后这个包装器被移回主线程,并发布一个事件,导致包装器实际创建QProgressDialog,这发生在主线程中。到目前为止,一切都很好。然后在辅助线程的一个长时间运行的循环中,我发出一个信号,期望它跨越线程边界并更新QProgressDialog。信号似乎确实到达了连接到信号的主循环的 lambda,但它 没有到达移动到主线程的对象。有趣的是,我可以发布一个事件来更新进度,所以我知道事件循环正在运行。我已经尝试了许多默认和排队连接的变体,在移动到新线程之前和之后连接,没有实际的 gui 组件(只是调试文本),基于堆栈与堆对象等。我的理解是 Qt 自动确定是否接收对象与发送对象位于不同的线程中,如果是,则发布由事件循环拾取的事件。问题可能是我在启动子线程后启动了主线程(QApplication::exec())?

在下面的代码中,main.cpp 中的 debug msg 正确地接收到事件并打印了消息,因此信号正在跨越线程边界。它只是没有到达我的包装对象。为什么不呢?

谢谢。

Main.cpp

#include "task.h"

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);

    TestClass worker("Tc1");
    QThread *thr = new QThread;

    QObject::connect(thr, &QThread::started, &worker, &TestClass::doWork);
    QObject::connect(&worker, qOverload<int,int>(&TestClass::progress), [](int x, int y){
        qDebug() << x << "out of" << y;});
    QObject::connect(&worker, &TestClass::done, []{qDebug() << "Done";});

    worker.moveToThread(thr);

    thr->start();
    a.exec();
    thr->wait();
    thr->quit();
    qDebug("Done");

}

任务.h

#ifndef TASK_H
#define TASK_H

#include <QApplication>
#include <QEvent>
#include <QDebug>
#include <QObject>
#include <QProgressBar>
#include <QProgressDialog>
#include <QString>
#include <QThread>
#include <functional>
#include <chrono>

static const QEvent::Type CREATE = static_cast<QEvent::Type>(QEvent::User + 0);
static const QEvent::Type SET_VALUE = static_cast<QEvent::Type>(QEvent::User + 1);

// Wraps QProgressDialog
// Create an instance, then move to another thread
class ProgressDialogWrapper : public QObject {
    Q_OBJECT
private:
    QProgressDialog *mWidget;
    QString mLabelText;
    QString mCancelButtonText;
    int mMinimum;
    int mMaximum;
    QWidget *mParent;
    const Qt::WindowFlags mF;
    void create() {mWidget = new QProgressDialog(mLabelText, mCancelButtonText,
                                                 mMinimum, mMaximum, mParent, mF);}
public:
    ProgressDialogWrapper(const ProgressDialogWrapper &) {}
    ~ProgressDialogWrapper() override {
        qDebug() << "ProgressDialogWrapper destructor";
    }
    ProgressDialogWrapper(const QString &labelText,
                          const QString &cancelButtonText,
                          int minimum,
                          int maximum,
                          QWidget *parent = nullptr,
                          Qt::WindowFlags f = Qt::WindowFlags()) :
        mWidget(nullptr),
        mLabelText(labelText),
        mCancelButtonText(cancelButtonText),
        mMinimum(minimum),
        mMaximum(maximum),
        mParent(parent),
        mF(f) {}
    ProgressDialogWrapper(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()) :
        mWidget(nullptr),
        mParent(parent),
        mF(f){}
    QProgressDialog *widget() {return mWidget;}
    // Receiving events works across thread boundary
    bool event(QEvent *e) override {
        switch(e->type()) {
            case QEvent::User:
                create();
                mWidget->show();
                return true;
            case SET_VALUE:
                mWidget->setValue(mWidget->value() + 1);
                return true;
            default:
                return false;
        }
    }

public slots:
    // These slots don't seem to work across thread boundary
    void show() {mWidget->show();}
    void setMaximum(int x) {mWidget->setMaximum(x);}
    void setMinimum(int x) {mWidget->setMinimum(x);}
    void setValue(int x) {
        qDebug() << "setValue";
        mWidget->setValue(x);
    }
    void requestValue() {emit requestedValue(mWidget->value());}
signals:
    void requestedValue(int);
};

// This didn't help
Q_DECLARE_METATYPE(ProgressDialogWrapper)


class TestClass : public QObject {
    Q_OBJECT
private:
    QString name;
public:
    TestClass() {}
    TestClass(QString n) : name(n) {}
    ~TestClass() override {
        qDebug() << "TestClass destructor";}
public slots:
    void doWork() {
        int loopcount = 10;
        ProgressDialogWrapper pdw("LabelText", "TheButton", 0, loopcount, nullptr);
        pdw.moveToThread(QApplication::instance()->thread());
        bool okc = QObject::connect(this, qOverload<int>(&TestClass::progress),
                         &pdw, &ProgressDialogWrapper::setValue,
                         Qt::QueuedConnection);
        qDebug() << "connect:" << okc;
        emit progress(5); // nope this doesn't work
        QApplication::postEvent(&pdw, new QEvent(CREATE));  // this does work

        std::chrono::milliseconds t(500);
        for (int i = 0; i < loopcount; i++) {
            // emitting to wrapper doesn't work -- no debug output            
            emit progress(i);
            
            // emitting to main.cpp lambda does work
            emit progress(i, loopcount);
            
            // Posting event works -- progress bar is updated
            QApplication::postEvent(&pdw, new QEvent(SET_VALUE));

            std::this_thread::sleep_for(t);
            QApplication::processEvents(); // no effect, I just made this up in desperation
        }
        emit done();
    }
signals:
    void progress(int);
    void progress(int, int);
    void done();
};


#endif // TASK_H

我也在考虑发送一个QEvent 派生类,它可以将 lambda 作为参数,然后在主线程中运行,但我还没有探索过。

【问题讨论】:

    标签: qt qt5


    【解决方案1】:

    考虑您的ProgressDialogWrapper::event 实施...

    bool event (QEvent *e) override
    {
        switch(e->type()) {
        case QEvent::User:
            create();
            mWidget->show();
            return true;
        case SET_VALUE:
            mWidget->setValue(mWidget->value() + 1);
            return true;
        default:
            return false;
        }
    }
    

    您只处理CREATESET_VALUE 情况:所有其他事件都被此ProgressDialogWrapper 实例有效地丢弃,包括排队连接使用的QEvent::MetaCall 事件。

    用...替换当前的实现

    bool event (QEvent *e) override
    {
        switch(e->type()) {
        case QEvent::User:
            create();
            mWidget->show();
            return true;
        case SET_VALUE:
            mWidget->setValue(mWidget->value() + 1);
            return true;
        default:
    
            /*
             * Defer to the base class implementation.
             */
            return return QObject::event(e);
        }
    }
    

    应该解决问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-11-27
      • 2012-04-03
      • 2011-04-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多