在非GUI线程中创建QWidget

时间:2012-11-01 19:57:43

标签: multithreading qt qt4

是的,我知道您无法使用非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线程中创建的GUI对象(QDialog)?

#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 个答案:

答案 0 :(得分:6)

Qt坚持要在GUI线程中创建小部件。它禁止将小部件移动到不同的线程,以防止它们存在于GUI线程之外。您上面的示例确实,实际上将BasicViewer移动到另一个线程;它只将BasicViewerWrapper移动到另一个线程。如果在BasicViewerWrapper::FunctionBasicViewer::Function内打印出指向包含线程的指针,则可以看到这一点:

std::cout << std::hex << thread() << std::endl;

如果您真的希望从GUI线程外部触发窗口小部件的创建,则更可取的是其他线程通知GUI线程创建您想要的窗口小部件。您可以从连接到创建窗口小部件的GUI线程中的插槽的非GUI线程发出信号,也可以在GUI线程中调用函数以使用QMetaObject::invokeMethod为您创建窗口小部件。

修改

不幸的是,如果您尝试在QMetaObject::invokeMethod之外执行调用,则无法在QObject以外的其他线程中调用方法。在过去,我试图通过将方法调用放在一个单独的类或函数中来解决可读性问题,但不可否认,它并不完美。

您的第3个示例无效,因为QObject::moveToThread不同步。在将对象实际移动到目标线程之前,控件必须返回到目标线程的事件循环。因此,在调用QCoreApplication::processEvents之后,您可能需要组合使用sleep语句和moveToThread调用。完成这些调用后,您应该通过basicViewerWrapper::create()致电basicViewerWrapper::exec()QMetaObject::invokeMethod