如何解决从非主线程引导 QT Gui
我知道以下不是正常的 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");
}
task.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,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",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 作为参数,然后让它在主线程中运行,但我还没有探索过。
解决方法
考虑您的 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;
}
}
您只处理 CREATE
和 SET_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);
}
}
应该解决问题。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。