12

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();
}
David Doria
  • 8,881
  • 14
  • 75
  • 133

1 Answers1

6

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.

RA.
  • 6,907
  • 1
  • 28
  • 34
  • I see - that was a bad demo because it didn't actually use a GUI element. I have edited the question with a demo that breaks like you suggest it should. However, from Function1(), I cannot emit a signal because I am not in a QObject. Is there any other option besides using invokeMethod? I used to use invokeMethod for things like this, but the readability was terrible. – David Doria Nov 01 '12 at 20:29
  • @DavidDoria I've edited my response to answer your latest questions. – RA. Nov 01 '12 at 21:09
  • by "at this point" do you mean "after the sleep and processEvents calls? Or just "what you should do is"? I tried just replacing basicViewerWrapper.create(); with QMetaObject::invokeMethod(&basicViewerWrapper, "create", Qt::QueuedConnection); but I get No such method BasicViewerWrapper::create() ? – David Doria Nov 01 '12 at 22:33
  • @DavidDoria I mean "after the sleep and processEvents calls". To fix the invokeMethod error, you'll need to declare create() and exec() as slots, or at least as Q_INVOKABLE. – RA. Nov 01 '12 at 22:35
  • Calling sleep sounds like a hack... is it? I tried QThread::currentThread()->msleep(1000); and it says msleep is protected. Some googling shows that you are supposed to subclass QThread to make it public, but surely the currently thread can't respond to that because it is only a QThread and not the subclass - how are you suggesting sleeping? And is there a different way? Ideally I would like to put all of this in a function of BasicViewerWrapper - does that help anything? – David Doria Nov 01 '12 at 22:44
  • Yes, sleeping here is a hack. The best way to accomplish this is without having to rely on moveToThread. In other words, the object that creates the message box and calls exec() on it is already in the GUI thread to begin with. For sleeping, there is no cross-platform way to sleep. On Linux, you can use nanosleep. On Windows, it is more complicated (see http://stackoverflow.com/questions/5801813/c-usleep-is-obsolete-workarounds-for-windows-mingw), but as I said before, sleeping is a hack. – RA. Nov 01 '12 at 22:58
  • One more thing - I just added another demo to the question. It uses a signal to trigger the GUI component, but isn't the GUI object (the QDialog) still created in the non-GUI thread? Can you explain why this works but the previous demo did not? – David Doria Nov 01 '12 at 23:51
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/18967/discussion-between-david-doria-and-ra) – David Doria Nov 02 '12 at 12:37