0

I'm now making a program that has to manage lots of QWidgets created when receiving requests, but I can't figure out how to create widgets in the QObject class. The compiler complains that "QObject: Cannot create children for a parent that is in a different thread."

Having on Google for an hour, I've tried many ways to solve the problem(here, here, and here), but none of them worked.

Here is some code about it:

// OSD.hpp
#ifndef OSD_HPP
#define OSD_HPP

#include <QWidget>
#include <QLabel>
#include <QVBoxLayout>

class OSD : public QWidget {
  Q_OBJECT
public:
  explicit OSD(QWidget *parent = nullptr);
  void setText(QString);
  const QString getText() const;

private:
  QLabel *text;
  QVBoxLayout *layout1;
};

#endif // OSD_HPP
// Teller.hpp
#ifndef Teller_HPP
#define Teller_HPP

#include "OSD.hpp"
#include <QObject>

class Teller : public QObject {
  Q_OBJECT
public:
  explicit Teller(int port, QObject *parent = nullptr);
  void SpawnNotification(std::string);
  ~Teller();
};

#endif // Teller_HPP

// Teller.cpp

class Worker : public QObject {
  Q_OBJECT
  OSD *o = nullptr;

public:
  Worker(){};
  ~Worker() {
    if (o) {
      o->deleteLater();
    }
  };
public slots:
  void process() {
    o = new OSD; // Problem here
    o->setText(QString("Hello"));
    o->show();
    QThread::sleep(1);
    emit finished();
  }
signals:
  void finished();
};

void Teller::SpawnNotification(std::string){
    QThread *thread = new QThread;
  Worker *worker = new Worker();
  worker->moveToThread(thread);
  connect(worker, &Worker::finished, worker, &Worker::deleteLater);
  connect(worker, &Worker::finished, thread, &QThread::deleteLater);
  connect(thread, &QThread::started, worker, &Worker::process);
  thread->start();
}

Am I missing something important?

pe200012
  • 37
  • 5

1 Answers1

1

In Qt the GUI lives in the main-thread. The Qt documentation says this:

GUI Thread and Worker Thread

As mentioned, each program has one thread when it is started. This thread is called the "main thread" (also known as the "GUI thread" in Qt applications). The Qt GUI must run in this thread. All widgets and several related classes, for example QPixmap, don't work in secondary threads. A secondary thread is commonly referred to as a "worker thread" because it is used to offload processing work from the main thread.

(Link: https://doc-snapshots.qt.io/qt5-5.12/thread-basics.html)

You would need to create the widgets on the main-thread and connect the worker's signals to the widget's slots. Similar to this (not tested..):

// Must be called in main thread
void Teller::SpawnNotification(std::string){
  QThread *thread = new QThread;
  QWidget *widget = new QWidget(nullptr, Qt::WA_DeleteOnClose); // Top-Level window
  Worker *worker = new Worker();
  worker->moveToThread(thread);
  connect(worker, &Worker::finished, worker, &Worker::deleteLater);
  connect(worker, &Worker::finished, thread, &QThread::deleteLater);
  connect(worker, &Worker::finished, widget, &QWidget::show);
  //connect(worker, &Worker::dataReady, widget, &QWidget::setData); // TODO
  connect(thread, &QThread::started, worker, &Worker::process);
  thread->start();
}

Edit (see comment below or https://doc.qt.io/qt-5/qt.html#ConnectionType-enum):

Also: Qt needs to know that the connection goes across different threads. Therefore you have to do the connection after moving "worker" to the new thread or explicitly use Qt::QueuedConnection.

Hope that helps!

Gregor Budweiser
  • 218
  • 3
  • 10
  • 2
    Your "also" is incorrect. It doesn't matter when the connection is done and `AutoConnect` (the default) is [almost] always enough. What to use (direct or queued) is determined during the signal emitting not when the connection is made. – ixSci Sep 01 '19 at 07:32