I exposed a pointer variable to qml like this:
Fruit:
class Fruit : public QObject
{
Q_OBJECT
Q_PROPERTY(int qualityGrade READ qualityGrade WRITE setQualityGrade NOTIFY qualityGradeChanged)
Q_PROPERTY(bool organic READ organic WRITE setOrganic NOTIFY organicChanged)
public:
int qualityGrade() const
{
return m_qualityGrade;
}
bool organic() const
{
return m_organic;
}
public slots:
void setQualityGrade(int qualityGrade)
{
if (m_qualityGrade == qualityGrade)
return;
m_qualityGrade = qualityGrade;
emit qualityGradeChanged(m_qualityGrade);
}
void setOrganic(bool organic)
{
if (m_organic == organic)
return;
m_organic = organic;
emit organicChanged(m_organic);
}
signals:
void qualityGradeChanged(int qualityGrade);
void organicChanged(bool organic);
private:
int m_qualityGrade = -1;
bool m_organic = false;
};
MyClass.h:
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(Fruit* featuredFruit READ featuredFruit WRITE setFeaturedFruit NOTIFY featuredFruitChanged)
public:
explicit MyClass(QObject *parent = nullptr);
~MyClass();
Fruit* featuredFruit() const
{
return m_featuredFruit;
}
public slots:
void setFeaturedFruit(Fruit* featuredFruit)
{
if (m_featuredFruit == featuredFruit)
return;
m_featuredFruit = featuredFruit;
emit featuredFruitChanged(m_featuredFruit);
}
signals:
void featuredFruitChanged(Fruit* featuredFruit);
private:
Fruit* m_featuredFruit = nullptr;
};
MyClass.cpp:
MyClass::MyClass(QObject *parent) : QObject(parent)
{
m_featuredFruit = new Fruit();
m_featuredFruit->setQualityGrade(2);
QTimer *timer = new QTimer();
connect(timer, &QTimer::timeout, this, [=]() {
//m_featuredFruit->deleteLater(); //<--- activating these two lines causes to force working featuredFruitChanged signal
//m_featuredFruit = new Fruit();
m_featuredFruit->setQualityGrade(5);
emit featuredFruitChanged(m_featuredFruit);
delete timer;
});
timer->start(5000);
}
MyClass::~MyClass()
{
m_featuredFruit->deleteLater();
m_featuredFruit = nullptr;
}
and I used it in QML as follow:
MyClass {
id: classObj
onFeaturedFruitChanged: console.log("original property shows an change");//<--- called as expected
}
Item {
property Fruit selectedFruit: classObj.featuredFruit //<--- binding qml defined property to C++ property
onSelectedFruitChanged: {
console.log("binded property recieved change signal");//<--- not called after changes!!!
alertAnimation.restart(); //<--- an example of usage
}
}
The problem is whenever I emit featuredFruitChanged, the binding qml property does not received change signal.
What is wrong?! Is this a Qt Framework bug? Any suggestion?
Also I tried overloading equality operator in C++ without success
Update:
OK, I add some more precisions to my sample code in order to reproduce problem easier.
A typo in my sample code fixed (thanks #ihor-drachuk). The problem exist yet.
Because you misspelled featuredFruit and wrote featuedFruit. Fix it and it will work.
Update: It should work if call setFeaturedFruit or if change it from QML. It will not work as you expect if change some property of featuredFruit even if it is selected
Update 2: QML call onChanged if changed value, value is pointer to object. So if pointer changed - then onChanged will be called. If something behind pointer changed - no.
But you can handle it with help of Connections:
Connections {
target: classObj.featuredFruit
onTargetChanged: console.warn("Target changed!");
onQualityGradeChanged: console.warn("onQualityGradeChanged!");
onOrganicChanged: console.warn("onOrganicChanged!");
}
Also you can add to Fruit some special signal like somethingChangedInMe and emit it in each setter. So, you can write in Connections just this signal handler:
Connections {
target: classObj.featuredFruit
onTargetChanged: console.warn("Target changed!");
onSomethingChangedInMe: console.warn("onSomethingChangedInMe!");
}
Related
I have a class that performs a request to the network and parses the data. How do I properly implement the request abort for it?
Imagine that I have such a class:
class MyClass
{
public:
...
void doRequest()
{
m_reply = m_manager.get(...);
QEventLoop waitForResponse;
connect(m_reply, &QNetworkReply::finished, &waitForResponse, &QEventLoop::quit);
waitForResponse.exec();
// Check if request was aborted (otherwise, application will crash)
if (m_reply == nullptr)
return;
// Check for network errors, write result to m_data and delete m_reply;
...
}
void abort()
{
if (m_reply != nullptr)
m_reply->abort();
}
QString data()
{
return m_data;
}
...
private:
QNetworkAccessManager *m_manager;
QPiinter<QNetworkReply> m_reply;
QString m_data;
}
Here is an example of its use by pressing the button:
class MainWindow : public QMainWindow
{
...
private slots:
MainWindow::on_myButton_pressed()
{
m_myClass->abort();
m_myClass->doRequest();
ui->myTextEdit->setText(m_myClass->data());
}
private:
MyClass m_myClass;
}
When you press the button, if the previous request is not completed, then it is canceled. This works. But under the hood in this case a new request writing data into the QTextEdit and exit the function, then old request returning from it's own loop and writes the same m_data to QTextEdit again.
Is it corrent? Maybe there is a more correct way to implement this?
Nested event loops are the root of all evil. It is much simpler to write a function like doRequest without pretending to the user that it is a synchronous function. It seems that you have already traced the convoluted control-flow that happens when you call abort() and you understand how subsequent calls to doRequest() end up being nested calls due to re-entering the event loop. If you restart your request multiple times, your stack would look something like (the stack grows downwards):
1. main function
2. main event loop
3. [...] (Qt functions)
4. MainWindow::on_myButton_pressed()
5. MyClass::doRequest()
6. QEventLoop::exec()
7. [...] (Qt functions)
8. MainWindow::on_myButton_pressed()
9. MyClass::doRequest()
10. QEventLoop::exec()
11. [...] (Qt functions)
12. MainWindow::on_myButton_pressed() and so on...
Each one of the calls to MainWindow::on_myButton_pressed() need to call ui->myTextEdit->setText() when its nested event loop exits.
An alternative would be make your functions fully asynchronous and rely on signals/slots if you need things to be executed when a particular operation finishes. Here is a minimal implementation for what you are trying to achieve:
#include <QtNetwork>
#include <QtWidgets>
/// A class responsible for communication with the web backend
class Backend : public QObject {
Q_OBJECT
public:
explicit Backend(QObject *parent = nullptr)
: QObject(parent), m_reply(nullptr) {}
public slots:
void doRequest() {
// abort and delete ongoing request (if any)
if (m_reply) {
m_reply->abort();
delete m_reply;
m_reply = nullptr;
}
emit textUpdated(QString());
// send request
QUrl url("http://wtfismyip.com/text");
QNetworkRequest request(url);
m_reply = m_manager.get(request);
// when the request is finished,
QObject::connect(m_reply, &QNetworkReply::finished, [this] {
// if the request ended successfully, read the received ip into m_lastData
if (m_reply->error() == QNetworkReply::NoError)
m_lastData = QString::fromUtf8(m_reply->readAll());
// otherwise, emit errorOccured() signal (if the request has not been
// actively canceled)
else if (m_reply->error() != QNetworkReply::OperationCanceledError)
emit errorOccured(m_reply->errorString());
// in all cases, emit updateText and do cleanup
emit textUpdated(m_lastData);
m_reply->deleteLater();
m_reply = nullptr;
});
}
void abort() {
if (m_reply != nullptr)
m_reply->abort();
}
signals:
void textUpdated(const QString &);
void errorOccured(const QString &);
private:
QNetworkAccessManager m_manager;
QNetworkReply *m_reply;
QString m_lastData;
};
/// A minimal widget that contains a QPushButton and a QLabel
class Widget : public QWidget {
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr) : QWidget(parent) {
m_layout.addWidget(&m_pushButton);
m_layout.addWidget(&m_label);
connect(&m_pushButton, &QPushButton::clicked, this, &Widget::buttonClicked);
}
signals:
void buttonClicked();
public slots:
void updateText(const QString &text) { m_label.setText(text); }
void showError(const QString &error) {
QMessageBox::warning(this, tr("Error"), error);
}
private:
QVBoxLayout m_layout{this};
QPushButton m_pushButton{"Retrieve Name"};
QLabel m_label;
};
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
Backend backend;
Widget widget;
// connect components
QObject::connect(&backend, &Backend::textUpdated, &widget,
&Widget::updateText);
QObject::connect(&backend, &Backend::errorOccured, &widget,
&Widget::showError);
QObject::connect(&widget, &Widget::buttonClicked, &backend,
&Backend::doRequest);
widget.show();
return a.exec();
}
#include "main.moc"
I have this class I intended to use in the context of a qml engine so in order to use property binding I setted up these Q_PROPERY macros. I want to use the MEMBER keyword and have the notify signal emitted automatically.
class InterfaceBackend : public QObject
{
Q_OBJECT
Q_PROPERTY(quint8 current_view MEMBER m_current_view NOTIFY sCurrentViewChanged)
Q_PROPERTY(quint8 future_view MEMBER m_future_view NOTIFY sFutureViewChanged)
public:
explicit InterfaceBackend(QObject *parent = 0);
~InterfaceBackend();
quint8 getCurrentView() { return this->m_current_view; }
quint8 getFutureView() { return this->m_future_view; }
private:
quint8 m_current_view;
quint8 m_future_view;
QByteArray m_selected_language;
public slots:
void onLanguageSelected(QByteArray language);
private slots:
signals:
void sCurrentViewChanged(quint8 current_view);
void sFutureViewChanged(quint8 future_view);
};
InterfaceBackend::InterfaceBackend(QObject *parent) : QObject(parent)
{
this->setObjectName("backend");
QObject::connect(this, &InterfaceBackend::sFutureViewChanged, []() {qDebug() << "sFutureViewChanged";});
this->m_current_view=1;
this->m_future_view=1;
}
InterfaceBackend::~InterfaceBackend()
{
}
void InterfaceBackend::onLanguageSelected(QByteArray language)
{
this->m_selected_language=language;
this->m_future_view=2;
}
qt docs say:
A NOTIFY signal is optional. If defined, it should specify one existing signal in that class that is emitted whenever the value of the property changes. NOTIFY signals for MEMBER variables must take zero or one parameter, which must be of the same type as the property. The parameter will take the new value of the property. The NOTIFY signal should only be emitted when the property has really been changed, to avoid bindings being unnecessarily re-evaluated in QML, for example. Qt emits automatically that signal when needed for MEMBER properties that do not have an explicit setter
But whenever I call the slot the signals never gets called nor the property is updated in the qml model, what's wrong!?
To give an answer that is technically more accurate:
The MEMBER in Q_PROPERTY will tell the moc (Meta object compiler) that when accessing the property via the meta object it should use the member directly instead of a getter or setter method. So the moc will the generate a setter method internally that sets the member and emits the signal - it basically just does the work of writing getters/setters for you. Since changing a member needs to emit the change signal, this is automatically done when the property is written from the meta object system. So, calling:
backend->setProperty("future_view", future_view);
will correctly emit the changed signal. This is the only guarantee that is given when using MEMBER. Changes, that are done via the meta property will trigger the change signal. This means if you would set future_view from QML directly, without the onLanguageSelected method, it would actually work.
In your example however you directly write a value to the member inside a special method - This will not trigger the signal automatically! (I mean, how should Qt even know you did that). So what you need to do is whenever you change the value of your member you need to emit the change signal yourself:
void onLanguageSelected(QByteArray language)
{
this->m_selected_language=language;
this->m_future_view=2;
emit sFutureViewChanged();
}
Edit: If you were trying prevent the properties from beeing written directly from QML, using MEMBER will not work! Use a getter instead and only register the getter with the property. Use the same code as above to write and change the properties:
Q_PROPERTY(quint8 future_view READ futureView NOTIFY sFutureViewChanged)
As you can see in this article:
New keyword in Q_PROPERTY: MEMBER let you bind a property to a class member without requiring to have a getter or a setter.
So you should remove your getters, and your result code will look like this
class InterfaceBackend : public QObject
{
Q_OBJECT
Q_PROPERTY(quint8 current_view MEMBER m_current_view NOTIFY sCurrentViewChanged)
Q_PROPERTY(quint8 future_view MEMBER m_future_view NOTIFY sFutureViewChanged)
public:
explicit InterfaceBackend(QObject *parent = 0)
: QObject(parent)
{
this->setObjectName("backend");
QObject::connect(this, &InterfaceBackend::sFutureViewChanged, []() { qDebug() << "sFutureViewChanged";});
this->m_current_view=1;
emit sCurrentViewChanged();
this->m_future_view=1;
emit sFutureViewChanged();
}
~InterfaceBackend() = default;
private:
quint8 m_current_view;
quint8 m_future_view;
QByteArray m_selected_language;
public slots:
void onLanguageSelected(QByteArray language) {
this->m_selected_language=language;
this->m_future_view=2;
emit sFutureViewChanged();
}
signals:
void sCurrentViewChanged();
void sFutureViewChanged();
};
Every Q_PROPERTY must have READ public method and WRITE public slot, also the signal will never automatically emited, you should emit it whenever MEMBER change.
class InterfaceBackend : public QObject
{
Q_OBJECT
Q_PROPERTY(quint8 current_view MEMBER m_current_view READ currentView WRITE setCurrentView NOTIFY sCurrentViewChanged)
Q_PROPERTY(quint8 future_view MEMBER m_future_view READ futureView WRITE setFutureView NOTIFY sFutureViewChanged)
public:
explicit InterfaceBackend(QObject *parent = 0);
~InterfaceBackend();
quint8 currentView() const
{
return m_current_view;
}
quint8 futureView() const
{
return m_future_view;
}
private:
quint8 m_current_view;
quint8 m_future_view;
QByteArray m_selected_language;
public slots:
void onLanguageSelected(QByteArray language);
void setCurrentView(quint8 current_view)
{
if (m_current_view == current_view)
return;
m_current_view = current_view;
emit sCurrentViewChanged(m_current_view);
}
void setFutureView(quint8 future_view)
{
if (m_future_view == future_view)
return;
m_future_view = future_view;
emit sFutureViewChanged(m_future_view);
}
private slots:
signals:
void sCurrentViewChanged(quint8 current_view);
void sFutureViewChanged(quint8 future_view);
};
InterfaceBackend::InterfaceBackend(QObject *parent) : QObject(parent)
{
this->setObjectName("backend");
QObject::connect(this, &InterfaceBackend::sFutureViewChanged, []() {qDebug() << "sFutureViewChanged";});
this->m_current_view=1;
this->m_future_view=1;
}
InterfaceBackend::~InterfaceBackend()
{
}
void InterfaceBackend::onLanguageSelected(QByteArray language)
{
this->m_selected_language=language;
this->m_future_view=2;
}
Spending some time, I got it working as I need, althought this is EXTREMELY UGLY and BY NO MEANS DECLARATIVE I think it is the only way to achive double binding, and maybe it will be helpful for other people approaching the problem. Note this is not an accepted answer, I still hope someone somewhere, perhaps in the future if and when Qt will be updated, could come up with a far more elegant solution.
Main disadvantages: longer and verbose code, one has to use setters everywhere and (try not forget)
The property system doesn't do anything automatically in the end, just states wich properties are exposed and wich are their getters/setters and change signals
c++
class InterfaceController : public QObject
{
Q_OBJECT
Q_PROPERTY(quint8 current_view READ getCurrentView WRITE setCurrentView NOTIFY sCurrentViewChanged)
Q_PROPERTY(quint8 future_view READ getFutureView WRITE setFutureView NOTIFY sFutureViewChanged)
public:
explicit InterfaceController(QObject *parent = 0);
//getters
quint8 getCurrentView() { return this->m_current_view; }
quint8 getFutureView() { return this->m_future_view; }
//setters
Q_INVOKABLE void setCurrentView(quint8 current_view) { if(this->m_current_view!=current_view) {this->m_current_view=current_view; emit sCurrentViewChanged(this->m_current_view);} }
Q_INVOKABLE void setFutureView(quint8 future_view) { if(this->m_future_view!=future_view) {this->m_future_view=future_view; emit sFutureViewChanged(this->m_future_view);} }
private:
quint8 m_current_view;
quint8 m_future_view;
QByteArray m_selected_language;
public slots:
void onLanguageSelected(QByteArray language);
private slots:
signals:
void sCurrentViewChanged(quint8 current_view);
void sFutureViewChanged(quint8 future_view);
};
InterfaceController::InterfaceController(QObject *parent) : QObject(parent)
{
this->m_current_view=1;
this->m_future_view=1;
}
void InterfaceController::onLanguageSelected(QByteArray language)
{
this->m_selected_language=language;
this->setFutureView(2);
}
QML
id: root
property int current_view: 1
property int future_view: 1
Connections {
target: root
onCurrent_viewChanged: { backend.setCurrentView(current_view); }
onFuture_viewChanged: { backend.setFutureView(future_view); }
}
Connections {
target: backend
onSCurrentViewChanged: { if(root.current_view!=current_view) {root.current_view=current_view;} }
onSFutureViewChanged: { if(root.future_view!=future_view) {root.future_view=future_view;} }
}
I need to send a signal from static callback function. I do the following:
.h
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow (QWidget* parent=0);
~MainWindow();
static MainWindow* getInstance();
private:
void emitSignal();
static MainWindow* m_instance;
signals:
void mysignal();
slots:
void print();
...
}
.cpp
MainWindow* MainWindow::m_instance = 0;
MainWindow::MainWindow(QWidget* parent):
QMainWindow(parent) {
...
}
MainWindow* MainWindow::getInstance() {
if (m_instance == 0)
m_instance = new MainWindow;
return m_instance;
}
void MainWindow::emitSignal() {
emit mysignal();
}
inside of callback function:
getInstance()->emitSignal();
and somewhere in code:
connect(MainWindow::getInstance(), SIGNAL(mysignal()), this, SLOT(print()));
But the slot print() is not executed. And no errors are printed. Where am I wrong?
The callback uses getInstance() and thus can definitely create a new widget if one doesn't exist yet. Then it's a matter of how well you understand the code that executes the callback. If the callback is made before you make the connection, then the slot won't be invoked. That's likely your problem. It is invalid to invoke the callback without an instance already in place, and you should assert that it is so.
Most likely, you do not need to create an instance of the main window on the fly. You're likely facing an XY problem where you think you need to do something, but you're so invested in a solution that doesn't work that you don't see the bigger problem you're trying to solve. Please clearly expose what exactly is your application - why are you creating the window on the fly?
It is also unnecessary to create a method that forwards to the signal, unless you really somehow need the signal to be private. I doubt that you do - don't make it private.
The code below demonstrates that what you're trying to do definitely works if you do it correctly:
// https://github.com/KubaO/stackoverflown/tree/master/questions/static-signal-48540601
#include <QtCore>
class Main : public QObject {
Q_OBJECT
static Main * m_instance;
public:
Main(QObject * parent = {}) : QObject(parent) {
Q_ASSERT(! hasInstance());
m_instance = this;
}
static Main * instance() {
if (! m_instance) m_instance = new Main;
return m_instance;
}
static bool hasInstance() { return m_instance; }
Q_SIGNAL void theSignal();
};
Main * Main::m_instance;
void callback() {
Q_ASSERT(Main::hasInstance());
// If the instance didn't exist here, nothing can receive the signal.
Main::instance()->theSignal();
}
int main()
{
int slotCalls = {};
Main object;
QObject::connect(&object, &Main::theSignal, [&]{ slotCalls ++; });
Q_ASSERT(slotCalls == 0);
callback();
Q_ASSERT(slotCalls == 1);
}
#include "main.moc"
Are you sure the signal connected to the slot?
To avoid typos you could use this syntax (as long as you dont overload signals/slots you are fine with that ):
connect(MainWindow::getInstance(), &MainWindow::mysignal, this, &MainWindow::print);
another hint/styleguide: name your signals like signal_mySignal and slots like slot_print to avoid typos and clear things up.
You can also call this->dumpObjectInfo(); from somewhere in your main (after the connects of course) and take a look at the output
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.
I am fairly new to Qt / QtQuick and I have to develop an application, which uses some sensor data that is received in a different thread over network periodically. This data should be used inside c++ for calculations and the latest data should also be displayed in QML. Everything is setup to be thread-safe inside c++ by using a mutex for protection and the data is visibly updated inside QML.
However, I have some concerns about thread-safety on the QML side and I cannot find information or an example on the web about this topic. Specifically I am concerned about returning a pointer (which was the only way to return a C++ object to QML I guess) instead of a value and therefore a copy of the object. Here is a minimal example demonstrating the concern:
// File data.h
#include <QObject>
class Data : public QObject {
Q_OBJECT
Q_PROPERTY(QString someData READ someData WRITE setSomeData NOTIFY someDataChanged)
public:
explicit Data(QObject* parent = nullptr)
:QObject(parent)
{
}
QString someData() const {
return _someData;
}
void setSomeData(const QString& value) {
if (_someData != value) {
_someData = value;
emit someDataChanged();
}
}
signals:
void someDataChanged();
private:
QString _someData;
}; // Data
// File: controller.h
#include <QObject>
#include <thread>
class Controller : public QObject {
Q_OBJECT
Q_PROPERTY(Data data READ data NOTIFY dataChanged)
public:
explicit Controller(QObject* parent = nullptr)
:QObject(parent)
,_running(false)
,_data(nullptr)
{
_data = new Data();
}
virtual ~Controller() {
delete _data;
}
void start() {
_running = true;
_thread = std::thread([this]() { _threadFunc(); });
}
void stop() {
_running = false;
if (_thread.joinable()) {
_thread.join();
}
}
Data* data() {
return _data;
}
signals:
void dataChanged();
private:
void _threadFunc() {
while (_running) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
_data.setSomeData("foo");
emit dataChanged();
}
}
bool _running;
std::thread _thread;
Data* _data;
}; // Controller
// File main.qml
import QtQuick 2.0
Rectangle {
width: 100
height: 100
Text {
anchors.centerIn: parent
text: Controller.data.someData
}
}
Data is a simple container holding a QString as property. The controller contains the property data and starts a thread that periodically updates data and emits changes as signal. The output will be displayed correctly, but it feels pretty unsafe to return a raw pointer. So my questions here are:
What happens if the data is written too fast and the thread manipulates the data at the same time when QML is using the pointer to update the visuals?
Are there alternatives to returning a raw pointer, e.g., something Qt offers for this purpose and something I have not found yet?
Is my kind of thinking wrong when it comes to using Qt/QML? I first developed the C++ backend (without any Qt parts) and now I am trying to connect it to the GUI. Maybe I should better design the backend around Qt or QML-friendly from start respectively?
Well, I think I found a solution to my problem: I am still sure that working on the same object will cause issues. I read a bit about QML ownership and found out that by using a Property, the ownership remains at the C++ side. By using a function returning a pointer, QML takes over ownership and will take care to delete the object later on. So what I did here was following if someone encounters the same issue some day:
// File data.h
#include <QObject>
class Data : public QObject {
Q_OBJECT
Q_PROPERTY(QString someData READ someData WRITE setSomeData NOTIFY someDataChanged)
public:
explicit Data(QObject* parent = nullptr)
:QObject(parent)
{
}
Data(const Data& data)
:QObject(data.parent)
,_someData(data.someData)
{
}
QString someData() const {
return _someData;
}
void setSomeData(const QString& value) {
if (_someData != value) {
_someData = value;
emit someDataChanged();
}
}
signals:
void someDataChanged();
private:
QString _someData;
}; // Data
// File: controller.h
#include <QObject>
#include <thread>
#include <mutex> // New
class Controller : public QObject {
Q_OBJECT
//Q_PROPERTY(Data data READ data NOTIFY dataChanged) // Removed
public:
explicit Controller(QObject* parent = nullptr)
:QObject(parent)
,_running(false)
,_data(nullptr)
{
_data = new Data();
}
virtual ~Controller() {
delete _data;
}
void start() {
_running = true;
_thread = std::thread([this]() { _threadFunc(); });
}
void stop() {
_running = false;
if (_thread.joinable()) {
_thread.join();
}
}
Q_INVOKABLE Data* data() { // Modified to be an invokable function instead of a property getter
std::lock_guard<std::mutex> lock(_mutex); // New
return new Data(*_data); // New
}
signals:
void dataChanged();
private:
void _threadFunc() {
while (_running) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::lock_guard<std::mutex> lock(_mutex); // New
_data.setSomeData("foo");
emit dataChanged();
}
}
bool _running;
std::thread _thread;
std::mutex _mutex; // New
Data* _data;
}; // Controller
// File main.qml
// NOTE: Controller is registered as context property alias 'controller'
import QtQuick 2.0
// Import the datatype 'data' to be used in QML
//import ...
Rectangle {
id: myRect
width: 100
height: 100
property Data data
Connections {
target: controller
onDataChanged: {
myRect.data = controller.data()
}
}
Text {
anchors.centerIn: parent
text: data.someData
}
}
Basically, I make sure to lock the object and to take a copy. This copy can then safely be used by QML and the QML engine will take care to delete the memory after usage. Furthermore, I create an instance of the Data object in QML and register the signal to fetch and assign the latest copy.