Spawn QWidgets inside a QObject's method - c++

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?

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!

Related

QThreads - Why allocate memory inside a constructor rather than having a normal class member?

http://doc.qt.io/qt-5/qthread.html
class Controller : public QObject
{
Q_OBJECT
QThread workerThread;
public:
Controller() {
Worker *worker = new Worker;
worker->moveToThread(&workerThread);
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(this, &Controller::operate, worker, &Worker::doWork);
connect(worker, &Worker::resultReady, this, &Controller::handleResults);
workerThread.start();
}
My way:
class Controller: public QObject
{
Q_OBJECT
public:
Worker objWorker;
QThread objQThread;
Controller();
~Controller();
public slots:
void receiveImage();
};
and
Controller::Controller()
{
objWorker.moveToThread( &objQThread );
connect( &objWorker, &Worker::imageReady, this, &Controller::receiveImage );
objQThread.start();
}
Is their method better than mine in some way? Which should be preferred in which case?
From: http://doc.qt.io/qt-5/qthread.html
From Qt 4.8 onwards, it is possible to deallocate objects that live in
a thread that has just ended, by connecting the finished() signal to
QObject::deleteLater().
This can be achieved only by making a pointer object of the Worker class and allocating memory to it.

how to connect signals and slots between two QObjects using movetoThread in two threads

I have two classes:A and B,both of them subclass from the QObject.
class A:public QObject
{
Q_OBJECT
public:
A();
~A();
}
i just referred to this article.
Then in the GUI (say the main Thread)I try to exchange data between A and B in different threads using
pA = new A();
QThread *workerThread = new QThread;
pA->moveToThread(workerThread);
connect(workerThread , SIGNAL(started()), pA, SLOT(doWork()));
connect(pA, SIGNAL(finished()), workerThread , SLOT(quit()));
connect(pA, SIGNAL(finished()), pA, SLOT(deleteLater()));
connect(workerThread , SIGNAL(finished()), workerThread , SLOT(deleteLater()));
workerThread ->start();
pB = new B();
connect(pA,SIGNAL(sigProduce(double)),pB,SLOT(slotConsume(double)));//I just don't know where to put this line
QThread *workerThread = new QThread;
pB ->moveToThread(workerThread);
connect(workerThread , SIGNAL(started()), pB , SLOT(doWork()));
connect(pB , SIGNAL(finished()), workerThread , SLOT(quit()));
connect(pB , SIGNAL(finished()), pB , SLOT(deleteLater()));
connect(workerThread , SIGNAL(finished()), workerThread , SLOT(deleteLater()));
workerThread ->start();
sigProduce(double) is a signal defined in the A object and slotConsume(double) is a public slot:
header of class B:
public slots:
void slotConsume(double);
so after the program started obj B can not get the double data from obj A.Does that connect(pA,pB) code's position matters?can anyone help me?THANKS.
Before I go into the details how to solve your problem, make sure the problem is valid in the first place. If the only reason you're using threads is because you want an infinite loop, then maybe you don't need threads at all. If the work inside the loop is light, then you can just do it in the main thread with a QTimer. Read this article to know more about this.
Anyway the problem you have is because you have an infinite loop in your doWork method, which is blocking the thread's event loop. Because of this you had to resort to calling processEvents. However this is not a good design. A better way would be to let the thread go back to the event loop by removing the infinite loop from doWork.
You can then simulate a loop with a timer, and stop the timer when you want to stop the thread. Or you can use QMetaObject::invokeMethod with a Qt::QueuedConnection to call the doWork method from the event loop, and use a bool condition top stop the work. In this example I used the invokeMethod option:
#include <QCoreApplication>
#include <QThread>
#include <QTimer>
#include <QDebug>
class Worker : public QObject
{
Q_OBJECT
public:
Worker(QObject *parent = 0) : QObject(parent), _stop(false) {}
public slots:
void doWork()
{
if(_stop)
return;
QThread::sleep(1); // simulating some heavy work here
emit calculationComplete(1564);
QMetaObject::invokeMethod(this, "doWork", Qt::QueuedConnection);
}
void stop()
{
_stop = true;
}
signals:
void calculationComplete(int result);
private:
bool _stop;
};
class ResultHandler : public QObject
{
Q_OBJECT
public:
ResultHandler(QObject *parent = 0) : QObject(parent) {}
public slots:
void handleResult(int result)
{
// do something with result
qDebug() << Q_FUNC_INFO << "Result:" << result;
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QThread thread_a;
Worker worker;
ResultHandler handler;
QObject::connect(&worker, &Worker::calculationComplete, &handler, &ResultHandler::handleResult);
QObject::connect(&thread_a, &QThread::started, &worker, &Worker::doWork);
QObject::connect(&thread_a, &QThread::finished, &worker, &Worker::stop);
worker.moveToThread(&thread_a);
thread_a.start();
QTimer::singleShot(5000, &thread_a, &QThread::quit); // just simulating an exit
int result = a.exec();
thread_a.quit();
thread_a.wait(5000);
return result;
}
#include "main.moc"

Thread Affinity: Cannot create children for a parent that is in a different thread

I have seen a similar question, but I feel that I am implementing the correct pattern and still I can't get it done!
Well, I have a Gui to start and stop data acquisition from a serial port and display necessary communication messages. To keep the Gui responsive, I move the worker to a thread. I tried to implement thread affinity, according to: How to Use QThread in the Right Way and How To Really, Truly Use QThreads. When I click on start button, I receive;
QWinEventNotifier: event notifiers cannot be enabled from another thread
QWinEventNotifier: event notifiers cannot be enabled from another thread
QWinEventNotifier: event notifiers cannot be enabled from another thread
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QSerialPort(0x142cd390), parent's thread is QThread(0x1259b070), current thread is QThread(0x142db1f0)
What am I missing? Here is a part of the code related to my question:
Worker header
#ifndef COMPORT_H
#define COMPORT_H
#include <QObject>
#include <QDebug>
#include <QSerialPort>
class QTimer;
class ComPort : public QObject
{
Q_OBJECT
public:
explicit ComPort(const QString &portName, QObject* parent = 0);
~ComPort();
private:
QSerialPort* port;
QString portMsg;
QByteArray responseData;
signals:
void finished();
private slots:
void onReadyRead();
void setupPort();
};
#endif // COMPORT_H
Worker cpp
#include "comport.h"
ComPort::ComPort(const QString &portName, QObject *parent)
:QObject(parent)
{
this->port = new QSerialPort(portName);
}
ComPort::~ComPort()
{
port->close();
delete port;
}
void ComPort::setupPort()
{
port->setBaudRate(QSerialPort::Baud19200);
port->setDataBits(QSerialPort::Data8);
port->setFlowControl(QSerialPort::NoFlowControl);
port->setParity(QSerialPort::NoParity);
port->setStopBits(QSerialPort::OneStop);
connect(port, SIGNAL(readyRead()), this, SLOT(onReadyRead()));
*SOME CODE HERE*
}
void ComPort::onReadyRead()
{
QByteArray bytes = port->readAll() ;
qDebug() << "bytes:" << bytes <<"\n";
responseData.append(bytes);
}
and Gui
#include "gui.h"
#include "ui_gui.h"
gui::gui(QWidget *parent) :
QDialog(parent),
ui(new Ui::gui)
{
ui->setupUi(this);
connect(ui->startButton, SIGNAL(clicked()), this, SLOT(OnstartButtonClicked()));
}
gui::~gui()
{
delete ui;
}
void gui::OnstartButtonClicked()
{
QThread* cThread = new QThread;
ComPort* cPort = new ComPort(QString("COM4"));
cPort->moveToThread(cThread);
connect(cPort, SIGNAL(finished()), cThread, SLOT(quit()));
connect(cPort, SIGNAL(finished()), cPort, SLOT(deleteLater()));
connect(cThread, SIGNAL(finished()), cThread, SLOT(deleteLater()));
connect(cThread, SIGNAL(started()), cPort, SLOT(setupPort()));
cThread->start();
}
Thank to the answer from Kim Bowles Sørhus, I solved my problem. I was creating a serial port in the constructor of ComPort that is called in the main thread. When the cPort object is moved to the cThread the QSerialPort still has its thread affinity set to the original thread. A solution is to create the QSerialPort in ComPort::setupPort.

create qt thread event loop

I am using Qt in order to write a GUI application.
A main thread is responsible for the GUI and creates an QThread in order to do some work with an object.
class Worker
{
void start() {
QTimer* timer = new Timer();
connect(timer,SIGNAL(timeout()),this,SLOT(do()));
}
void do() {
//do some stuff
emit finished();
}
}
class GUI
{
//do some GUI work then call startWorker();
void startWorker() {
QThread* thread = new Thread();
Worker* worker = new Worker();
worker->moveToThread(thread);
connect(thread, SIGNAL(started()), worker, SLOT(start()));
connect(worker, SIGNAL(finished()), workerthread, SLOT(quit()));
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
}
}
Now I have several problems:
The timer in my worker class does not work. Maybe it is because the new thread has no event loop, but I have no idea how to create such one. I tried
connect(workerthread, SIGNAL(started()), workerthread, SLOT(exec()));
but it does not work either.
When I try to wait on the new thread, the signal is never sent
class GUI
{
void exit() {
thread->wait();
}
}
I think it also is because there is no event loop and because of that no signal is emitted.
Does anybody have an idea how to solve these problems?
why not use qthreadpool, than you make your task class inherits from qrunnable and qobject, this way you can use signals and slots to pass data from one thread to another, is much simpler to implement, and increase performance from not recreating a thread or having one sleeping all the time
class myTask : public QObject, public QRunnable{
Q_OBJECT
protected:
void run(); //where you actually implement what is supposed to do
signals:
void done(int data);//change int to whatever data type you need
}
//moc click example, or use a timer to call this function every x amount of time
void button_click(){
myTask *task = new myTask();
task->setAutoDelete(true);
connect(task,SIGNAL(done(int)),this,SLOT(after_done(int)),Qt::QueuedConnection);
QThreadPool::globalInstance()->start(task);
}
by default you application gets 1 thread automatically, which you can use to handle the graphic, than use the qthreadpool to process the data/object on demand, you can even set the max amount of threads your application can use to process new request, the others will stay in a queue until one thread is freed
QThreadPool::globalInstance()->setMaxThreadCount(5);
this is a sample code for you :
QThread* thread = new QThread();
Worker* worker = new Worker(3000);
worker->moveToThread(thread);
QObject::connect(thread, SIGNAL(started()), worker, SLOT(start()));
thread->start();`
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(qint32,QObject *parent = 0);
qint32 myTime;
signals:
void workFinished();
public slots:
void doWork();
void start();
private:
QTimer *timer;
};
#include "worker.h"
#include <QTimer>
#include <QDebug>
Worker::Worker(qint32 t,QObject *parent) :
QObject(parent)
{
myTime=t;
}
void Worker::start()
{
timer = new QTimer();
timer->start(myTime);
qDebug()<<QString("start work in time:%1").arg(myTime);
connect(timer,SIGNAL(timeout()),this,SLOT(doWork()));
}
void Worker::doWork()
{
qDebug()<<"dowork";
timer->stop();
emit workFinished();
}
Debug results :
start work in time:3000
I hope this helps you.

What is the proper way to separate the view layer from the logic layer in QT?

My mainwindow have a side GUI with a QGraphicsView in the center, there is a logic class which make different calculations which triggered by the GUI and effects the QGraphicsView.
Some of the calculations are heavy which make the GUI go to sleep, there is a QProgressBar and some other Qt items which provides some data while the calculations are been made, so when the GUI process goes to sleep those items show there updated result only when the process has finished. I understand that it because the logic class and the UI are under the same process,
I was trying to correct this by doing: My old question and Maya's Programming Blog
But I came to understanding that both are not sufficient to my code as I have several methods that run heavy calculations and some of them returning values. Both talking about doing something like this: connect(thread, SIGNAL(started()), worker, SLOT(process()));... than thread->start();, but in my code there is no single main process, so if I want to work in that way, from what I understand I need to create a thread before each process method, move the logic class to this thread and than connect the process method with the thread start method, which not sounds to me as the proper way to do this.
So I ask for a way to fully separate the logic layer from the view layer, so any method which invoked the logic layer will run on a different process (which is the same for all logic class methods), so the view layer won't go to sleep.
Note: there is no problems of synchronicity, when a calculation been made it's the only one which work at a time.
An example of my problem:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "Logic/worker.h"
namespace Ui {
class MainWindow;
#define MAXLOOP 1000000
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
signals:
private slots:
void startProcessing1();
void processing1Done();
void on_pushButton_exit_clicked();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
/////////////////////////////////////////////////////////////////////////////////
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QThread"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->progressBar_1->setVisible(false);
ui->progressBar_1->setMaximum(MAXLOOP);
ui->progressBar_1->setMinimum(0);
connect(ui->pushButton_1, SIGNAL(clicked()), this, SLOT(startProcessing1()));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::startProcessing1()
{
ui->progressBar_1->setVisible(true);
Worker *worker = new Worker(MAXLOOP);
QThread* thread = new QThread;
worker->moveToThread(thread);
connect(worker, SIGNAL(finished1Hide()), this, SLOT(processing1Done()));
connect(worker, SIGNAL(changePbar1(int)), ui->progressBar_1, SLOT(setValue(int)));
connect(thread, SIGNAL(started()), worker, SLOT(process1()));
connect(worker, SIGNAL(finished1()), thread, SLOT(quit()));
connect(worker, SIGNAL(finished1()), worker, SLOT(deleteLater()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
}
void MainWindow::processing1Done()
{
ui->progressBar_1->setVisible(false);
}
void MainWindow::on_pushButton_exit_clicked()
{
this->close();
}
/////////////////////////////////////////////////////////////////////////////////
#ifndef WORKER_H
#define WORKER_H
#include <QObject>
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(int maxLoop, QObject *parent = 0);
signals:
void finished1();
void finished1Hide();
void changePbar1(int val);
public slots:
void process1();
private:
int m_maxLoop;
};
#endif // WORKER_H
/////////////////////////////////////////////////////////////////////////////////
#include "worker.h"
Worker::Worker(int maxLoop, QObject *parent) :
QObject(parent)
{
m_maxLoop = maxLoop;
}
void Worker::process1()
{
int sum = 0;
for(int i = 0; i < m_maxLoop; ++i)
{
emit changePbar1(i);
sum += i;
}
emit finished1();
emit finished1Hide();
}