在非 GUI 线程中创建 QWidget
Posted
技术标签:
【中文标题】在非 GUI 线程中创建 QWidget【英文标题】:Creating a QWidget in a non-GUI thread 【发布时间】:2012-11-01 19:57:43 【问题描述】:是的,我知道你不能在非 GUI 线程中使用 GUI 的东西。但是,能够创建一个 QWidget 对象,将它发送到 GUI 线程,然后向它发送信号似乎是合理的。但是,当我尝试这样做时,我收到无法移动小部件的错误。但是,这似乎有效:
#include <iostream>
#include <QApplication>
#include <QtConcurrentRun>
#include <QDialog>
class BasicViewer : public QDialog
Q_OBJECT
public:
void Function(const float a)
std::cout << a << std::endl;
;
struct BasicViewerWrapper : public QObject
Q_OBJECT
public:
BasicViewer WrappedBasicViewer;
void Function(const float a)
WrappedBasicViewer.Function(a);
;
#include "main.moc" // For CMake's automoc
void Function2()
BasicViewerWrapper basicViewerWrapper;
basicViewerWrapper.moveToThread(QCoreApplication::instance()->thread());
basicViewerWrapper.Function(2.0f);
void Function1()
Function2();
int main(int argc, char *argv[])
QApplication app(argc, argv);
QtConcurrent::run(Function1);
std::cout << "End" << std::endl;
return app.exec();
我创建了一个与 QWidget 具有相同 API 的包装类,它存储了我想直接创建的 QWidget 的实例。我被允许创建该包装器,将其移动到 GUI 线程,然后使用它。我的问题是,有没有办法做到这一点而不必编写这个包装器?这似乎很乏味,而且由于这个概念有效,我不明白为什么不能直接完成。有什么想法吗?
----------- 编辑 ---------------
第一个例子很糟糕,因为它没有尝试对 GUI 元素做任何事情。此示例确实会生成“无法为位于不同线程中的父级创建子级。”
#include <iostream>
#include <QApplication>
#include <QtConcurrentRun>
#include <QMessageBox>
class BasicViewer : public QMessageBox
Q_OBJECT
public:
;
struct BasicViewerWrapper : public QObject
Q_OBJECT
public:
BasicViewer WrappedBasicViewer;
void exec()
WrappedBasicViewer.exec();
;
#include "main.moc" // For CMake's automoc
void Function2()
BasicViewerWrapper basicViewerWrapper;
basicViewerWrapper.moveToThread(QCoreApplication::instance()->thread());
basicViewerWrapper.exec();
void Function1()
Function2();
int main(int argc, char *argv[])
QApplication app(argc, argv);
QtConcurrent::run(Function1);
return app.exec();
----------- 编辑 2 ----
我认为这会起作用,因为成员对象是在 Wrapper 的线程被移动之后创建的:
#include <iostream>
#include <QApplication>
#include <QtConcurrentRun>
#include <QMessageBox>
class BasicViewer : public QMessageBox
Q_OBJECT
public:
;
struct BasicViewerWrapper : public QObject
Q_OBJECT
public:
BasicViewer* WrappedBasicViewer;
void exec()
WrappedBasicViewer->exec();
void create()
WrappedBasicViewer = new BasicViewer;
;
#include "main.moc" // For CMake's automoc
void Function2()
BasicViewerWrapper basicViewerWrapper;
basicViewerWrapper.moveToThread(QCoreApplication::instance()->thread());
basicViewerWrapper.create();
basicViewerWrapper.exec();
void Function1()
Function2();
int main(int argc, char *argv[])
QApplication app(argc, argv);
QtConcurrent::run(Function1);
return app.exec();
不幸的是,它没有。谁能解释一下为什么?
--------------- 编辑 3 --------
我不确定为什么会这样?它使用信号触发GUI组件,但GUI对象(QDialog)不是还在非GUI线程中创建吗?
#include <iostream>
#include <QApplication>
#include <QtConcurrentRun>
#include <QMessageBox>
class DialogHandler : public QObject
Q_OBJECT
signals:
void MySignal(int* returnValue);
public:
DialogHandler()
connect( this, SIGNAL( MySignal(int*) ), this, SLOT(MySlot(int*)), Qt::BlockingQueuedConnection );
void EmitSignal(int* returnValue)
emit MySignal(returnValue);
public slots:
void MySlot(int* returnValue)
std::cout << "input: " << *returnValue << std::endl;
QMessageBox* dialog = new QMessageBox;
dialog->addButton(QMessageBox::Yes);
dialog->addButton(QMessageBox::No);
dialog->setText("Test Text");
dialog->exec();
int result = dialog->result();
if(result == QMessageBox::Yes)
*returnValue = 1;
else
*returnValue = 0;
delete dialog;
;
#include "main.moc" // For CMake's automoc
void MyFunction()
DialogHandler* dialogHandler = new DialogHandler;
dialogHandler->moveToThread(QCoreApplication::instance()->thread());
int returnValue = -1;
dialogHandler->EmitSignal(&returnValue);
std::cout << "returnValue: " << returnValue << std::endl;
int main(int argc, char *argv[])
QApplication app(argc, argv);
QtConcurrent::run(MyFunction);
std::cout << "End" << std::endl;
return app.exec();
【问题讨论】:
【参考方案1】:Qt 坚持在 GUI 线程中创建小部件。它禁止将小部件移动到不同的线程,以防止它们存在于 GUI 线程之外。您上面的示例确实不,实际上,将BasicViewer
移动到不同的线程;它只会将BasicViewerWrapper
移动到不同的线程。如果您打印出指向 BasicViewerWrapper::Function
和 BasicViewer::Function
中包含线程的指针,您可以看到这一点:
std::cout << std::hex << thread() << std::endl;
如果您确实希望从 GUI 线程之外触发小部件的创建,则更建议其他线程通知 GUI 线程创建您想要的小部件。您可以从连接到创建小部件的 GUI 线程中的插槽的非 GUI 线程发出信号,或者您可以调用 GUI 线程中的函数来使用 QMetaObject::invokeMethod
为您创建小部件。
编辑
不幸的是,如果您尝试在 QObject
之外执行调用,则无法在 QMetaObject::invokeMethod
之外的其他线程中调用方法。过去,我尝试通过将方法调用放在单独的类或函数中来解决可读性问题,但不可否认,这并不完美。
您的第三个示例不起作用,因为 QObject::moveToThread
不同步。在对象实际移动到目标线程之前,控制必须返回目标线程的事件循环。因此,在调用moveToThread
之后,您可能需要结合使用睡眠语句和对QCoreApplication::processEvents
的调用。在这些电话之后,您可能应该通过QMetaObject::invokeMethod
致电basicViewerWrapper::create()
和basicViewerWrapper::exec()
。
【讨论】:
我明白了 - 这是一个糟糕的演示,因为它实际上并没有使用 GUI 元素。我已经用一个演示编辑了这个问题,就像你建议的那样。但是,从 Function1() 开始,我无法发出信号,因为我不在 QObject 中。除了使用invokeMethod还有其他选择吗?我曾经用 invokeMethod 来处理这样的事情,但是可读性很差。 @DavidDoria 我已经编辑了我的回复以回答您的最新问题。 “此时”是指“在 sleep 和 processEvents 调用之后?还是只是“你应该做的是”?我尝试将 basicViewerWrapper.create(); 替换为 QMetaObject::invokeMethod (&basicViewerWrapper, "create", Qt::QueuedConnection); 但我得到 No such method BasicViewerWrapper::create() ? @DavidDoria 我的意思是“在 sleep 和 processEvents 调用之后”。要修复 invokeMethod 错误,您需要将 create() 和 exec() 声明为插槽,或者至少声明为 Q_INVOKABLE。 调用 sleep 听起来像个黑客……是吗?我试过 QThread::currentThread()->msleep(1000);它说 msleep 受到保护。一些谷歌搜索表明你应该继承 QThread 以使其公开,但当前线程肯定无法响应,因为它只是一个 QThread 而不是子类 - 你如何建议睡觉?还有不同的方法吗?理想情况下,我想把所有这些都放在 BasicViewerWrapper 的一个函数中——这有什么帮助吗?以上是关于在非 GUI 线程中创建 QWidget的主要内容,如果未能解决你的问题,请参考以下文章
从在非 GUI 线程中运行的 C 代码获取 QInputDialog::getText() 结果
使用 terraform 在非默认 VPC 中创建 AWS RDS 实例