Creating a QWidget in a non-GUI thread

房东的猫 提交于 2019-12-18 20:49:11

问题


Yes, I know that you cannot use GUI things from non-GUI threads. However, it seems reasonable to be able to create a QWidget object, send it to the GUI thread, and then send signals to it. However, when I try to do so, I get errors that widgets cannot be moved. However, this seems to works:

#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();
}

I have created a wrapper class with the same API as the QWidget that stores an instance of the QWidget I wanted to create directly. I AM allowed to create that wrapper, move it to the GUI thread, and then use it. My question is, is there a way to do this without having to write this wrapper? It seems quite tedious, and since the concept works, I don't understand why it cannot be done directly. Any thoughts?

----------- EDIT ---------------

The first example was a bad one, because it did not attempt to do anything with GUI elements. This example indeed generates "Cannot create children for a parent that is in a different thread."

#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();
}

----------- EDIT 2 ----------------

I thought this would work, since the member object gets created after the thread of the Wrapper has been moved:

#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();
}

Unfortunately, it does not. Can anyone explain why?

--------------- EDIT 3 --------------------

I'm unsure why this works? It uses a signal to trigger the GUI component, but isn't the GUI object (the QDialog) still created in the non-GUI thread?

#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 insists that widgets be created within the GUI thread. It disables moving widgets to different threads to prevent them from existing outside of the GUI thread. Your example above does not, in fact, move the BasicViewer to a different thread; it only moves BasicViewerWrapper to a different thread. You can see this if you print out the pointer to the containing thread within BasicViewerWrapper::Function and BasicViewer::Function:

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

If you really wish to trigger the creation of widgets from outside the GUI thread, it is more advisable for other threads to notify the GUI thread to create the widgets that you desire. You can either emit a signal from the non-GUI thread that connects to a slot in the GUI thread that creates the widgets, or you can invoke a function within the GUI thread to create the widgets for you using QMetaObject::invokeMethod.

EDIT

Unfortunately, there is no way to invoke a method in a different thread other than QMetaObject::invokeMethod if you are attempting to perform the invocation outside of a QObject. In the past, I've tried to tackle readability by placing the method invocation in a separate class or function, but admittedly, it's not perfect.

Your 3rd example is not working because QObject::moveToThread is not synchronous. Control must return to the destination thread's event loop before the object is actually moved to the destination thread. As such, you probably need a combination of a sleep statement and a call to QCoreApplication::processEvents after calling moveToThread. After these calls, you should probably call basicViewerWrapper::create() and basicViewerWrapper::exec() via QMetaObject::invokeMethod.



来源:https://stackoverflow.com/questions/13184555/creating-a-qwidget-in-a-non-gui-thread

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!