Solved Emiting signals from singletons
-
Hello,
Is it possible at all to emit signals from singleton classes that could be handled in QML?
If yes, what is the preferred way to do so?
Thank you in advance.
-
@stehlo said in Emiting signals from singletons:
If yes, what is the preferred way to do so?
the same way like from any other Item
Why should it be different for a singleton? -
@raven-worx said in Emiting signals from singletons:
Why should it be different for a singleton?
Well, I would expect it to work then, especially considering that I use the same methodology.
Therefore, something tells me that there IS a difference.
-
@stehlo said in Emiting signals from singletons:
Well, I would expect it to work then, especially considering that I use the same methodology.
Therefore, something tells me that there IS a difference.I would beg to differ, as I'm using more than one Singelton in my Project and I have no problems with signals in QML or c++
Can you share some code ?
The cpp & h file of your singleton, the QML instantiation and the c++ one?
-
@j-hilk said in Emiting signals from singletons:
Can you share some code ?
Thank you. Yes, of course.
(Please note, they will be just simplified excerpts due to the amount of unrelated code in those files.)//database.h ... class Database : public QObject { Q_OBJECT explicit Database(QObject *parent = nullptr); Q_DISABLE_COPY(Database) ~Database(); ... void logAndEmitCriticalError(const QString msg); ... public: static Database* getInstance(); static QObject* singletonTypeProvider(QQmlEngine *engine, QJSEngine *scriptEngine); ... signals: void errorMessage(const QString msg); ... } ...
//database.cpp ... Database::Database(QObject *parent) : QObject(parent) { init(); } Database::~Database() { auto db = QSqlDatabase::database(); if (db.isOpen()) { db.close(); qInfo("Closed database connection."); } } ... Database* Database::getInstance() { static Database* inst = new Database(); return inst; } ... QObject* Database::singletonTypeProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return Database::getInstance(); } ... void Database::logAndEmitCriticalError(const QString msg) { qCritical("%s", qUtf8Printable(msg)); emit errorMessage(msg); } ...
//main.cpp ... qmlRegisterSingletonType<Database>("com.xyz.database", 1, 0, "Database", Database::singletonTypeProvider); ...
//Main.qml ... import com.xyz.database 1.0 ... Connections { id: db target: Database // onErrorMessage: { // toast.show((msg.length > 0) // ? msg // : qsTr("Database: unknown error.")); // } } ...
Now, please note:
- I can't use
onErrorMessage
handler above, as I could do for non-singletonQ_OBJECT
s. - Everything in the Database class works correctly upon initialisation, except for the emission of the signals that are handled in QML or, respectively, signals might be emitted but they aren't handled in QML.
- Trying to fiddle with
Q_PROPERTY(... NOFIFY ...)
doesn't make any sense here, because there is no property to export. - And, naturally, I can't use the following for singleton:
Database { onErrorMessage: { toast.show((msg.length > 0) ? msg : qsTr("Database: unknown error.")); } }
Therefore, obviously, I am missing something – and this something is what constitutes the difference between emiting signals for non-singletons and for singletons.
Thank you in advance.
- I can't use
-
First off, thanks for the detailed information. Not every poster makes the effort 😆
@stehlo said in Emiting signals from singletons:
Database* Database::getInstance()
{
static Database* inst = new Database();
return inst;
}
...
QObject* Database::singletonTypeProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
return Database::getInstance();
}I'm sorry, but this is not a correct singleton pattern for Qt/QML
this:
Database* Database::getInstance() { static Database* inst = new Database(); return inst; }
is constantly creating new instances, I think you forgot theconst
declaration then it should work.
(See post 8 & 10)here, an excerpt from one of my classes
//.h private: explicit DeviceInformation(QObject *parent = nullptr); static QObject *DeviceInfo; public: static DeviceInformation *getInstance(QObject *parent =nullptr){ if(DeviceInfo) return qobject_cast<DeviceInformation*>(DeviceInformation::DeviceInfo); auto instance = new DeviceInformation(parent); DeviceInfo = instance; return instance; } static QObject *instance(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(scriptEngine) if(DeviceInfo){ Q_UNUSED(engine) return DeviceInfo; } DeviceInfo = new DeviceInformation(); engine->setObjectOwnership(DeviceInfo,QQmlEngine::CppOwnership); return DeviceInfo; }
//.cpp mainly only used for the static pointer initialization QObject* DeviceInformation::DeviceInfo = nullptr; DeviceInformation::DeviceInformation(QObject *parent) : QObject(parent) { reset(); }
-
@j-hilk said in Emiting signals from singletons:
this:
Database* Database::getInstance()
{
static Database* inst = new Database();
return inst;
}is constantly creating new instances, I think you forgot the const declaration then it should work.
Thank you for looking into this issue for me.
However, I am very sorry but I have to disagree.
According to The C++PL 4th Ed., page 314 (12.1.8 Local Variables) – "If a local variable is declared
static
, a single statically allocated object will be used to represent that variable in all calls of the function. It will be initialized only the first time a thread of execution reaches its definition."Thus, unless I don't understand Bjarne's explanation or Qt breaks such definition in some way, I can't see how
const
could make any difference here.On top of that, the manner of creating the singleton doesn't change the fact that the signal is not connectable in QML as demonstrated in my previous post.
===
Update 1:
I just tested theconst
and the error says:- cannot initialize return object of type 'QObject *' with an rvalue of type 'const Database *'
The function
qmlRegisterSingletonType
expects a non-const
QObject *
. -
@stehlo
ok,here's a minimal working example
#ifndef MYSINGLETON_H #define MYSINGLETON_H #include <QObject> #include <QTimer> #include <QQmlEngine> class mySingleton : public QObject { Q_OBJECT private: explicit mySingleton(QObject *parent = nullptr); static QObject *MySingleton; public: static mySingleton *getInstance(QObject *parent =nullptr){ if(MySingleton) return qobject_cast<mySingleton*>(mySingleton::MySingleton); auto instance = new mySingleton(parent); MySingleton = instance; return instance; } static QObject *instance(QQmlEngine *engine, QJSEngine *scriptEngine) { Q_UNUSED(scriptEngine) if(MySingleton){ Q_UNUSED(engine) return MySingleton; } MySingleton = new mySingleton(); engine->setObjectOwnership(MySingleton,QQmlEngine::CppOwnership); return MySingleton; } signals: void errorMessage(const QString &str); }; #endif // MYSINGLETON_H
#include "mysingleton.h" #include <QTimer> #include <QTime> static const QString newError("new Error %1"); QObject* mySingleton::MySingleton = nullptr; mySingleton::mySingleton(QObject *parent) : QObject(parent) { QTimer *t(new QTimer(this)); connect(t, &QTimer::timeout, this, [=]()->void{ emit errorMessage(newError.arg(QTime::currentTime().toString())); }); t->start(1000); }
#include <QApplication> #include <QQmlApplicationEngine> #include "mysingleton.h" #include "QQmlContext" #include <QDebug> int main(int argc, char *argv[]) { QApplication app(argc, argv); qmlRegisterSingletonType<mySingleton>("MySingleton", 1, 0, "Singel", mySingleton::instance); mySingleton *cppSingelton = mySingleton::getInstance(); QQmlApplicationEngine engine; QObject::connect(cppSingelton, &mySingleton::errorMessage, &engine, [](const QString error)->void{ qDebug() << "Cpp Slot" << error; }); engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); if (engine.rootObjects().isEmpty()) return -1; return app.exec(); }
import QtQuick 2.12 import QtQuick.Window 2.12 import MySingleton 1.0 Window { visible: true width: 400 height: 750 title: qsTr("Hello World") Connections{ target: Singel onErrorMessage: console.log("QML slot", str) } }
-
@j-hilk said in Emiting signals from singletons:
here's a minimal working example
Thank you so much. Your kind help is greatly appreciated.
I have carefully examined the construction of all the relevant parts and ported the necessary ones into my code base.
I can confirm that everything works well now.
I have also observed your use of (A) passing by reference of constants and (B) setting the object's ownership [to C++]. I have used these to improve the quality of my code. In respect to the latter, I have read up on the topic yesterday and I was therefore glad to see you using it today, which confirmed the notion that it would be a good idea to use it eventually.
On top of that, I have noticed during today's testing that many signal emissions were made before the ApplicationWindow was completed and this was the reason for non-handling of those signals in QML.