Bypassing Qt signaling to avoid class-to-class signaling - c++

I'm trying to figure out how I could work with Qt signals in a little bit different way.
I have a working example of such:
Game::Game(QObject *parent)
: QObject{parent}
, m_timer{nullptr}
{
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, &Game::run);
m_timer->setinterval(300);
m_timer->start();
}
Above signaling works fine and calls the Game::run regularly as desired. Now I am trying to replicate this with this bypass (for the sake of learning and it could be useful for more complicated moments especially in unit testing)
Abstract class:
class ISignalHandler
{
public:
virtual void onTimeout() = 0;
protected:
virtual ~ISignalHandler() = default;
};
and another class MyTimer.cpp:
MyTimer::MyTimer(QTimer *parent)
: QTimer{parent}
{
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, &MyTimer::onTimeout);
}
MyTimer::~MyTimer()
{
delete m_timer;
m_timer = nullptr;
}
void MyTimer::registerTimerSignal(ISignalHandler* callback)
{
m_callback = callback;
}
void MyTimer::onTimeout()
{
if (m_callback != nullptr)
{
m_callback->onTimeout();
}
}
and finally Game class:
//header:
class Game : public QObject, public ISignalHandler
{
Q_OBJECT
public:
explicit Game(QObject *parent = nullptr);
~Game();
//cpp:
Game::Game(QObject *parent)
: QObject{parent}
, m_timer{nullptr}
{
m_timer = new MyTimer();
m_timer->registerTimerSignal(this);
m_timer->setinterval(300);
m_timer->start();
}
void Game::onTimeout()
{
run();
}
I expected that the MyTimer class will repeat within itself, and the signal will be handled to Game class so ::run will be continuously called. Where is a mistake here?

Related

How to connect signal from a different class?

// splashscreen.h
class SplashScreen : public QMainWindow
{
Q_OBJECT
public:
explicit SplashScreen(QWidget *parent = nullptr);
~SplashScreen();
QTimer *mtimer;
public slots:
void update();
private:
Ui_SplashScreen *ui;
};
// app.h
#include "splashscreen.h"
class App: public QMainWindow
{
Q_OBJECT
public:
App(QWidget *parent = nullptr);
~App();
SplashScreen s;
private:
Ui::AppClass ui;
};
// app.cpp
App::App(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
QGraphicsOpacityEffect* eff = new QGraphicsOpacityEffect();
s.centralWidget()->setGraphicsEffect(eff);
QPropertyAnimation* a = new QPropertyAnimation(eff, "opacity");
a->setDuration(2000);
a->setStartValue(0);
a->setEndValue(1);
a->start(QPropertyAnimation::DeleteWhenStopped);
s.show();
connect(a, &QAbstractAnimation::finished, this, [this]
{
auto *timer = new QTimer();
this->s.mtimer = timer;
QObject::connect(timer, SIGNAL(timeout()), this->s, SLOT(update()));
timer->start(100);
});
}
I'm getting an error at this line: QObject::connect(timer, SIGNAL(timeout()), this->s, SLOT(update()));
no instance of overloaded function "QObject::connect" matches the argument list
I think it's mismatching the class signal, as this passed to the lambda refers to App not SplashScreen.
When i try to pass s (SplashScreen) to the lambda:
connect(a, &QAbstractAnimation::finished, this, [s]
{ ... }
I get an error: App::s is not a variable.
I'm confused, what is the proper way to connect in this case?
In App class, s is an instance, not a pointer to an instance. Function connect needs pointer, not reference.
Use these syntax should help:
QObject::connect(timer, &QTimer::timeout, &s, &SplashScreen::update);
Use these syntax:
QObject::connect(timer, &QTimer::timeout, this, &SplashScreen::update);

How to run a QThread from QWizardPage and access field()

I need some advice to access the field(QString name) variable in QWizardPage from a QThread. I'm building some kind of an installer and I want to do the installing work in a separate Thread.
My purpose:
When reached the commit/install page, I want to execute code to do the "installing" and update the QWizardPage with my progress, until its finished.
The install function is dependent on many field() variables from other QWizardPages. Therefore I tried to execute this install function from a QThread, which is defined in an inner class from my QWizardPage. The problem is, the field()-function i a non-static member and so it's not working. And so I'm out of ideas to run my install-function parallel to my WizardPage.
I tried something like this:
InstallPage.h
class InstallPage : public QWizardPage
{
Q_OBJECT
class WorkerThread : public QThread
{
Q_OBJECT
void run() override;
};
public:
InstallPage(QWidget *parent = 0);
private:
QLabel *lProgress;
WorkerThread *installer;
void install();
};
InstallPage.c
InstallPage::InstallPage(QWidget *parent)
: QWizardPage(parent)
{
...
installer = new WorkerThread(this);
installer->start();
}
void InstallPage::WorkerThread::run()
{
if(field("checkBox1").ToBool())
{
doStuff();
}
}
//QT-Creator says at field("checkBox1"):
//error: call to non-static member function without an object argument
I'm also open for any other idea to make my installer work. Maybe someone knows something I haven't thought of.
Another approach is to create a worker (QObject) that lives in another thread that performs the heavy task and notifies the status of that task through signals:
#include <QtWidgets>
class InitialPage: public QWizardPage
{
public:
InitialPage(QWidget *parent = nullptr): QWizardPage(parent)
{
QSpinBox *spinbox = new QSpinBox;
QLineEdit *lineedit = new QLineEdit;
QVBoxLayout *lay = new QVBoxLayout(this);
lay->addWidget(spinbox);
lay->addWidget(lineedit);
registerField("value1", spinbox);
registerField("value2", lineedit);
}
};
class InstallWorker: public QObject
{
Q_OBJECT
public:
InstallWorker(QObject *parent=nullptr): QObject(parent)
{
}
public Q_SLOTS:
void install(int param1, const QString & param2)
{
Q_EMIT started();
for(int i=0; i < 100; i++){
qDebug() << __PRETTY_FUNCTION__ << i << param1 << param2;
QThread::msleep(100);
Q_EMIT progressChanged(i);
}
qDebug()<< __PRETTY_FUNCTION__ << "finished";
Q_EMIT finished();
}
Q_SIGNALS:
void started();
void progressChanged(int value);
void finished();
};
class InstallPage: public QWizardPage
{
Q_OBJECT
public:
InstallPage(QWidget *parent = nullptr): QWizardPage(parent),
label(new QLabel), progressbar(new QProgressBar)
{
QVBoxLayout *lay = new QVBoxLayout(this);
lay->addWidget(label);
lay->addWidget(progressbar);
progressbar->setMinimum(0);
progressbar->setMaximum(100);
thread = new QThread(this);
worker.moveToThread(thread);
connect(&worker, &InstallWorker::started, this, &InstallPage::onStarted);
connect(&worker, &InstallWorker::finished, this, &InstallPage::onFinished);
connect(&worker, &InstallWorker::progressChanged, this, &InstallPage::onProgressChanged);
thread->start();
}
~InstallPage(){
thread->quit();
thread->wait();
}
void initializePage(){
start_install();
}
private Q_SLOTS:
void start_install(){
int param1 = field("value1").toInt();;
QString param2 = field("value2").toString();
QMetaObject::invokeMethod(&worker, "install", Qt::QueuedConnection, Q_ARG(int, param1), Q_ARG(QString, param2));
}
void onStarted(){
for(QWizard::WizardButton which: {QWizard::BackButton, QWizard::NextButton, QWizard::CancelButton})
if(QAbstractButton * button = wizard()->button(which))
button->setEnabled(false);
}
void onFinished(){
for(QWizard::WizardButton which: {QWizard::BackButton, QWizard::NextButton, QWizard::CancelButton})
if(QAbstractButton * button = wizard()->button(which))
button->setEnabled(true);
wizard()->next();
}
void onProgressChanged(int value){
progressbar->setValue(value);
label->setNum(value);
}
private:
InstallWorker worker;
QThread *thread;
QLabel *label;
QProgressBar *progressbar;
};
class FinalPage: public QWizardPage
{
public:
FinalPage(QWidget *parent = nullptr): QWizardPage(parent)
{
}
};
#include "main.moc"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWizard wizard;
wizard.addPage(new InitialPage);
wizard.addPage(new InstallPage);
wizard.addPage(new FinalPage);
wizard.show();
return app.exec();
}

Serialport in a separate QThread

I'd like to insert the serial port in a separate QThread, but the application crashes. I wrote the following C++ classes
Worker.h
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = 0);
signals:
void finished();
void error(QString err);
public slots:
void process();
};
class WorkerInterface : public QObject
{
Q_OBJECT
public:
explicit WorkerInterface(QObject *parent = nullptr);
~WorkerInterface();
serialport *readSerialPort();
signals:
void threadStoppedChanged();
public slots:
void errorString(QString errorMsg);
void stopThread();
private:
QThread m_thread;
serialPort *m_serial;
};
Worker::Worker(QObject *parent)
: QObject(parent)
{
}
void Worker::process()
{
emit finished();
}
Worker.cpp
WorkerInterface::WorkerInterface(QObject *parent)
: QObject(parent)
, m_thread(this)
{
serialPort::serialPortMaster = new serialPort(nullptr);
m_serial = serialPort::serialPortMaster;
serialPort::serialPortMaster->moveToThread(&m_thread);
connect(&m_thread, SIGNAL(started()),serialPort::serialPortMaster, SLOT(Init()));
m_thread.start();
}
WorkerInterface::~WorkerInterface()
{
m_thread.quit();
m_thread.wait(1000);
if (!m_thread.isFinished())
m_thread.terminate();
}
void WorkerInterface::errorString(QString errorMsg)
{
qDebug() << "error" << errorMsg;
}
void WorkerInterface::stopThread()
{
m_thread.quit();
m_thread.wait(1000);
if (!m_thread.isFinished())
m_thread.terminate();
emit threadStoppedChanged();
}
serialPort* WorkerInterface::readSerialPort()
{
return(m_serialPort);
}
In the main.cpp I wrote the following code:
WorkerInterface workerInterface;
engine.rootContext()->setContextProperty("newserial", workerInterface.readSerialPort());
QQmlComponent component(&engine,QUrl(QStringLiteral("qrc:/Pages/Content/Qml/main.qml")));
QObject *qmlObject = component.create();
When the code arrives at the last instruction in main.cpp, the application crashes and in the QT creator console there is the following messages:
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QSerialPort(0xee18c0), parent's thread is QThread(0xc8d8b0), current thread is QThread(0x7fffffffdc60)
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QSerialPort(0xee18c0), parent's thread is QThread(0xc8d8b0), current thread is QThread(0x7fffffffdc60)
QQmlEngine: Illegal attempt to connect to serialPort(0xee1710) that is in a different thread than the QML engine QQmlApplicationEngine(0x7fffffffdc30.
Could someone help me to solve the crash?
Many thanks in advance.
Assuming that you have device which responds with text, the best and simplest way to do it is something like this:
class IODevLineReader
{
Q_OBJECT
public:
explicit IODevLineReader(QObject *parent);
public signals:
void lineWasReceived(const QString &line);
public slots:
void onReadyRead() {
QIODevice *dev = qobject_cast<QIODevice *>(sender());
while (dev && dev->canReadLine()) {
auto lineBytes = dev->readLine();
emit lineWasReceived(lineBytes);
}
}
};
Just connect QSerialPort::readyRead() to IODevLineReader::onReadyRead() and connect some slot to IODevLineReader::lineWasReceived() signal and you are done without use of threads.
And if you still insist to use thread, just use same object tree and move it to specified thread.

start qtimer from another class faces segmentation fault

I'm facing a problem using QTimer. The program closes with a segmentation fault in run time and when I exclude the "timer" from code, it runs properly. Here's the code:
Class A : public QObject, public QGraphicsPixmapItem{
Q_OBJECT
public:
A(QPixmap pic){
this->setPixmap(pic);
}
void start(){
timer = new QTimer();
timer->start(1000);
connect(timer, SIGNAL(timeout()), this, SLOT(moveObject()));
}
public slots:
void moveObject(){
moveTimer = new QTimer();
moveTimer->start(20);
connect(moveTimer, SIGNAL(timeout()), this, SLOT(changePosition()));
}
void changePosition(){
//a couple of things are done here
}
private:
QTimer *timer;
QTimer *moveTimer;
}
Class B : QGraphicsView{
Q_OBJECT
public:
B(QWidget *parent = 0) : QGraphicsView(parent){
a = new A(QPixmap("a.png"));
void go(){
a->start();
}
private:
A *a;
}
P.S. I delete moveTimer in changePosition as long as the object stops moving and recall moveObject so that it can move towards the next target.

Qt GUI doesn't work with std::thread as I expect

The core of my project is independent of GUI framework that's why I prefer std::thread. But Qt gives me an error when thread is using.
The inferior stopped because it received a signal from the operating system.
Signal name: SIGSEGV
Signal meaning: Segmentation fault
//MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <thread>
#include <mutex>
#include <QMainWindow>
namespace Ui { class MainWindow; }
struct Observer
{
virtual void notify() = 0;
};
class Core
{
public:
std::thread *run()
{
std::thread thread(&Core::runP, this);
thread.detach();
return &thread;
}
void setObserver(Observer *observer) { _observer = observer; }
int ii() const { return _ii; }
void nextIi() { _ii++; }
void lock() { _mutex.lock(); }
bool tryLock() { return _mutex.try_lock(); }
void unlock() { _mutex.unlock(); }
private:
void runP()
{
for (int i = 1; i <= 1000; i++) {
if (i % 10 == 0) {
lock();
nextIi();
unlock();
notify();
}
}
}
void notify() { _observer->notify(); } //!!!
Observer *_observer;
int _ii;
std::mutex _mutex;
};
struct MwObserver : public Observer
{
explicit MwObserver(struct MainWindow *mainWindow) { _mainWindow = mainWindow; }
virtual void notify();
MainWindow *_mainWindow;
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow() { delete _ui; }
void upd();
public slots:
void run() { _core.run(); }
private:
Ui::MainWindow *_ui;
MwObserver _observer;
Core _core;
};
inline void MwObserver::notify() { _mainWindow->upd(); }
#endif
-
//MainWindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
_ui(new Ui::MainWindow),
_observer(this)
{
_ui->setupUi(this);
connect(_ui->pushButtonRun, SIGNAL(clicked(bool)), this, SLOT(run()));
}
void MainWindow::upd()
{
_core.lock();
setWindowTitle(QString::number(_core.ii()));
_core.unlock();
}
There are multiple problems here, first and most obvious was already noted by perencia. You are returning a pointer to stack variable. In c++ terms it's unacceptable.
Secondly. The crash comes from not using std::thread, but from race condition. The Qt event loop does not know about you mutex, so your setWindowTitle call is introducing a race, that leads to crash.
You need to use QMetaObject::invokeMethod to post function to the Qts event loop.
Example:
change
inline void MwObserver::notify() { _mainWindow->upd(); }
to
inline void MwObserver::notify() {
if(!QMetaObject::invokeMethod(_mainWindow, "upd", Qt::QueuedConnection))
std::cerr << " Failed to invoke method" << std::endl;
}
additional includes may apply
This updates the GUI from a thread different then the GUI thread! Which is not allowed.
Why not to use QThread and a signal/slot mechanism to update your window title. The Qt framework does the thread switching automatically.
class Core : public QObject
{
Q_OBJECT
public:
explicit Core(QObject * parent = 0) : QObject(parent) {}
signals:
void notify();
public slots:
void nextIi() { _ii++; }
void runP()
{
for (int i = 1; i <= 1000; i++) {
if (i % 10 == 0) {
nextIi();
notify();
}
}
}
private:
Q_DISABLE_COPY(Core);
int _ii;
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void run() {_th.start();}
void upd(int ii) {setWindowTitle(QString::number(ii));}
private:
Ui::MainWindow *_ui;
Core _core;
QThread _th;
};
//MainWindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
_ui(new Ui::MainWindow),
_observer(this)
{
_ui->setupUi(this);
connect(_ui->pushButtonRun, SIGNAL(clicked(bool)), this, SLOT(run()));
connect(&_core, SIGNAL(notify(int)), this, SLOT(upd(int)));
_core.moveToThread(&_th);
}
MainWindow::~MainWindow()
{
delete _ui;
_th.quit();
_th.wait(1000);
}
You are creating thread on the stack and returning a pointer to that. After run() that pointer is no longer valid.
Aside from returning pointer to stack variable and updating GUI from thread object that is not known for QT. I don't see from your code, where you set up _observer member of Core class. There is no setObserver call for _core member of MainWindow class.
So consructor of MainWindow class calls consructor of _core member, but after that _core._observer contains garbage. I think this is the cause of your Segmentaion Fault in call of notify method of Core class.
The answers to all the problems have already been given, let me summarize.
The program crash has nothing to do with the threading, The problem is that the _observer in the _core member of MainWindowis not set. A call to setObserver must be added.
explicit MainWindow( QWidget *parent = nullptr ) :
QMainWindow( parent ),
_observer( this )
{
_core.setObserver( &_observer );
}
This will lead to the next problem, that the observer actually calls the udp message from another thread, causing a UI update in a different thread context. To solve this, it is easiest to use Qt's Qt::QueuedConnection. To enable this we must make upt() a slot.
public slots:
void run();
void upd();
Then we can either call it using QMetaObject::invokeMethod in
inline void MwObserver::notify()
{
QMetaObject::invokeMethod( _mainWindow, "upd", Qt::QueuedConnection );
}
or use a signal / slot connection by deriving MwObserver from QObject, giving it a signal, and connect that signal to the upd slot and raising the signal in notify.
struct MwObserver
: public QObject
, public Observer
{
Q_OBJECT;
signals:
void sigUpd();
public:
explicit MwObserver( MainWindow *mainWindow );
virtual void notify()
MainWindow *_mainWindow;
};
void MwObserver::notify()
{
sigUpd();
}
MwObserver::MwObserver( MainWindow *mainWindow )
{
_mainWindow = mainWindow;
connect( this, SIGNAL(sigUpd()), _mainWindow, SLOT(upd()) )
}
Disclaimer: I haven't used Qt in some time but with X/XMotif on Linux/UNIX the GUI MUST run in the 'main-thread', not spawned threads. Maybe this applies to your situation. Just a thought, have your GUI code run in the main-thread.
The best approach is to wrap pure C++ code with QObejct instance and fire signals when this objects receive some notification from pure C++ code.
SO in your case:
class MwObserver : public QObject, public Observer
{
Q_OBJECT
public:
explicit MwObserver(QObject *parent)
: QObject(parent)
{}
signals:
void SomeEvent();
protected:
// Observer
void notify() {
emit SomeEvent();
}
};
Now MainWindow should connect some slot to signal provided this way and everything should work out of the box (Qt will do thread jumping behind the scenes).
In your code form comment the crash is caused by invalid use of temporary object. This is INVALID C++ code no mater what kind of object is returned:
std::thread *run()
{
std::thread thread(&Core::runP, this);
thread.detach();
return &thread;
}
You cant return a pointer to local object of the function method, since this object becomes invalid immediately when you return a function. This is basic C++ knowledge.