Singletons in QML
In QML, a singleton is an object which is created at most once per engine. In this guide, we'll explain how to create singletons and how to use them. We'll also provide some best practices for working with singletons.
How can singletons be created in QML?
There are two separate ways of creating singletons in QML. You can either define the singleton in a QML file, or register it from C++.
Defining singletons in QML
To define a singleton in QML, you first have to add
pragma Singleton
to the top of your file. There's one more step: You will need to add an entry to the QML module's qmldir file.
Using qt_add_qml_module (CMake)
When using CMake, the qmldir is automatically created by qt_add_qml_module. To indicate that the QML file should be turned into a singleton, you need to set the QT_QML_SINGLETON_TYPE
file property on it:
set_source_files_properties(MySingleton.qml
PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
You can pass multiple files at once to set_source_files_properties
:
set(plain_qml_files MyItem1.qml MyItem2.qml FancyButton.qml ) set(qml_singletons MySingleton.qml MyOtherSingleton.qml ) set_source_files_properties(${qml_singletons} PROPERTIES QT_QML_SINGLETON_TYPE TRUE) qt_add_qml_module(myapp URI MyModule QML_FILES ${plain_qml_files} ${qml_singletons} )
Note: set_source_files_properties needs to be called before qt_add_qml_module
Without qt_add_qml_module
If you aren't using qt_add_qml_module
, you'll need to manually create a qmldir file. There, you'll need to mark your singletons accordingly:
module MyModule singleton MySingleton 1.0 MySingleton.qml singleton MyOtherSingleton 1.0 MyOtherSingleton.qml
See also Object Type Declaration for more details.
Defining singletons in C++
There are multiple ways of exposing singletons to QML from C++. The main difference depends on whether a new instance of a class should be created when needed by the QML engine; or if some existing object needs to be exposed to a QML program.
Registering a class to provide singletons
The simplest way of defining a singleton is to have a default-constructible class, which derives from QObject and mark it with the QML_SINGLETON and QML_ELEMENT macros.
class MySingleton : public QObject { Q_OBJECT QML_SINGLETON QML_ELEMENT public: MySingleton(QObject *parent = nullptr) : QObject(parent) { // ... } };
This will register the MySingleton
class under the name MySingleton
in the QML module to which the file belongs. If you want to expose it under a different name, you can use QML_NAMED_ELEMENT instead.
If the class can't be made default-constructible, or if you need access to the QQmlEngine in which the singleton is instantiated, it is possible to use a static create function instead. It must have the signature MySingleton *create(QQmlEngine *, QJSEngine *)
, where MySingleton
is the type of the class that gets registered.
class MyNonDefaultConstructibleSingleton : public QObject { Q_OBJECT QML_SINGLETON QML_NAMED_ELEMENT(MySingleton) public: MyNonDefaultConstructibleSingleton(QJSValue id, QObject *parent = nullptr) : QObject(parent) , m_symbol(std::move(id)) {} static MyNonDefaultConstructibleSingleton *create(QQmlEngine *qmlEngine, QJSEngine *) { return new MyNonDefaultConstructibleSingleton(qmlEngine->newSymbol(u"MySingleton"_s)); } private: QJSValue m_symbol; };
Note: The create function takes both a QJSEngine and a QQmlEngine parameter. That is for historical reasons. They both point to the same object which is in fact a QQmlEngine.
Exposing an existing object as a singleton
Sometimes, you have an existing object that might have been created via some third-party API. Often, the right choice in this case is to have one singleton, which exposes those objects as its properties (see Grouping together related data). But if that is not the case, for example because there is only a single object that needs to be exposed, use the following approach to expose an instance of type MySingleton
to the engine. We first expose the Singleton as a foreign type:
struct SingletonForeign { Q_GADGET QML_FOREIGN(MySingleton) QML_SINGLETON QML_NAMED_ELEMENT(MySingleton) public: inline static MySingleton *s_singletonInstance = nullptr; static MySingleton *create(QQmlEngine *, QJSEngine *engine) { // The instance has to exist before it is used. We cannot replace it. Q_ASSERT(s_singletonInstance); // The engine has to have the same thread affinity as the singleton. Q_ASSERT(engine->thread() == s_singletonInstance->thread()); // There can only be one engine accessing the singleton. if (s_engine) Q_ASSERT(engine == s_engine); else s_engine = engine; // Explicitly specify C++ ownership so that the engine doesn't delete // the instance. QJSEngine::setObjectOwnership(s_singletonInstance, QJSEngine::CppOwnership); return s_singletonInstance; } private: inline static QJSEngine *s_engine = nullptr; };
Then we set SingletonForeign::s_singletonInstance
before we start the first engine
SingletonForeign::s_singletonInstance = getSingletonInstance(); QQmlApplicationEngine engine; engine.loadFromModule("MyModule", "Main");
Note: It can be very tempting to simply use qmlRegisterSingletonInstance in this case. However, be wary of the pitfalls of imperative type registration listed in the next section.
Imperative type registration
Before Qt 5.15, all types, including singletons were registered via the qmlRegisterType
API. Singletons specifically were registered via either qmlRegisterSingletonType or qmlRegisterSingletonInstance. Besides the minor annoyance of having to repeat the module name for each type and the forced decoupling of the class declaration and its registration, the major problem with that approach was that it is tooling unfriendly: It was not statically possible to extract all the necessary information about the types of a module at compile time. The declarative registration solved this issue.
Note: There is one remaining use case for the imperative qmlRegisterType
API: It is a way to expose a singleton of non-QObject type as a var
property via the QJSValue based qmlRegisterSingletonType
overload . Prefer the alternative: Expose that value as the property of a (QObject
) based singleton, so that type information will be available.
Accessing singletons
Singletons can be accessed both from QML as well as from C++. In QML, you need to import the containing module. Afterwards, you can access the singleton via its name. Reading its properties and writing to them inside JavaScript contexts is done in the same way as with normal objects:
import QtQuick import MyModule Item { x: MySingleton.posX Component.onCompleted: MySingleton.ready = true; }
Setting up bindings on a singletons properties is not possible; however, if it is needed, a Binding element can be used to achieve the same result:
import QtQuick import MyModule Item { id: root Binding { target: MySingleton property: "posX" value: root.x } }
Note: Care must be taken when installing a binding on a singleton property: If done by more than one file, the results are not defined.
Guidelines for (not) using singletons
Singletons allow you to expose data which needs to be accessed in multiple places to the engine. That can be globally shared settings, like the spacing between elements, or data models which need to be displayed in multiple places. Compared to context properties which can solve a similar use case, they have the benefit of being typed, being supported by tooling like the QML Language Server, and they are also generally faster at runtime.
It is recommended not to register too many singletons in a module: Singletons, once created, stay alive until the engine itself gets destroyed and come with the drawbacks of shared state as they are part of the global state. Thus consider using the following techniques to reduce the amount of singletons in your application:
Grouping together related data
Adding one singleton for each object which you want to expose adds quite some boiler plate. Most of the time, it makes more sense to group data you want to expose together as properties of a single singleton. Assume for instance that you want to create an ebook reader where you need to expose three abstract item models, one for local books, and two for remote sources. Instead of repeating the process for exposing existing objects three times, you can instead create one singleton and set it up before starting the main application:
class GlobalState : QObject { Q_OBJECT QML_ELEMENT QML_SINGLETON Q_PROPERTY(QAbstractItemModel* localBooks MEMBER localBooks) Q_PROPERTY(QAbstractItemModel* digitalStoreFront MEMBER digitalStoreFront) Q_PROPERTY(QAbstractItemModel* publicLibrary MEMBER publicLibrary) public: QAbstractItemModel* localBooks; QAbstractItemModel* digitalStoreFront; QAbstractItemModel* publicLibrary }; int main() { QQmlApplicationEngine engine; auto globalState = engine.singletonInstance<GlobalState *>("MyModule", "GlobalState"); globalState->localBooks = getLocalBooks(); globalState->digitalStoreFront = setupLoalStoreFront(); globalState->publicLibrary = accessPublicLibrary(); engine.loadFromModule("MyModule", "Main"); }
Use object instances
In the last section, we had the example of exposing three models as members of a singleton. That can be useful when either the models need to be used in multiple places, or when they are provided by some external API over which we have no control. However, if we need the models only in a single place it might make more sense have them as an instantiable type. Coming back to the previous example, we can add an instantiable RemoteBookModel class, and then instantiate it inside the book browser QML file:
// remotebookmodel.h class RemoteBookModel : public QAbstractItemModel { Q_OBJECT QML_ELEMENT Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged) // ... }; // bookbrowser.qml Row { ListView { model: RemoteBookModel { url: "www.public-lib.example"} } ListView { model: RemoteBookModel { url: "www.store-front.example"} } }
Passing initial state
While singletons can be used to pass state to QML, they are wasteful when the state is only needed for the initial setup of the application. In that case, it is often possible to use QQmlApplicationEngine::setInitialProperties. You might for instance want to set Window::visibility to fullscreen if a corresponding command line flag has been set:
QQmlApplicationEngine engine; if (parser.isSet(fullScreenOption)) { // assumes root item is ApplicationWindow engine.setInitialProperties( { "visibility", QVariant::fromValue(QWindow::FullScreen)} ); } engine.loadFromModule("MyModule, "Main");
© 2024 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.