在非 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::FunctionBasicViewer::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() 结果

Excel VBA 在非默认日历中创建会议

在不同的线程中创建 QApplication

使用 terraform 在非默认 VPC 中创建 AWS RDS 实例

如何在非 Android Gradle 项目中创建 Android 库?

如何在非脏内存中创建链接器解析的数据表