【问题标题】:Creating a QWidget in a non-GUI thread在非 GUI 线程中创建 QWidget
【发布时间】: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();
}

【问题讨论】:

    标签: multithreading qt qt4


    【解决方案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 的一个函数中——这有什么帮助吗?
    猜你喜欢
    • 1970-01-01
    • 2011-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-11
    • 2017-08-30
    • 1970-01-01
    • 2012-04-03
    相关资源
    最近更新 更多