Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qmlsingletons.qdoc
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
3
4/*!
5\page qml-singleton.html
6\title Singletons in QML
7\brief A guide for using singletons in QML
8
9In QML, a singleton is an object which is created at most once per
10\l{QQmlEngine}{engine}. In this guide, we'll
11\l{How can singletons be created in QML}{explain how to create} singletons
12and \l{Accessing singletons}{how to use them}. We'll also provide some
13best practices for working with singletons.
14
15\section1 How can singletons be created in QML?
16
17There are two separate ways of creating singletons in QML. You can either define
18the singleton in a QML file, or register it from C++.
19
20\section2 Defining singletons in QML
21To define a singleton in QML, you first have to add
22\code
23pragma Singleton
24\endcode
25to the top of your file.
26There's one more step: You will need to add an entry to the QML module's
27\l{Module Definition qmldir Files}{qmldir file}.
28
29\section3 Using qt_add_qml_module (CMake)
30When using CMake, the qmldir is automatically created by \l{qt_add_qml_module}.
31To indicate that the QML file should be turned into a singleton, you need to set
32the \c{QT_QML_SINGLETON_TYPE}
33file property on it:
34\code [cmake]
35set_source_files_properties(MySingleton.qml
36 PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
37\endcode
38
39You can pass multiple files at once to \c{set_source_files_properties}:
40\code [cmake]
41set(plain_qml_files
42 MyItem1.qml
43 MyItem2.qml
44 FancyButton.qml
45)
46set(qml_singletons
47 MySingleton.qml
48 MyOtherSingleton.qml
49)
50set_source_files_properties(${qml_singletons}
51 PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
52qt_add_qml_module(myapp
53 URI MyModule
54 QML_FILES ${plain_qml_files} ${qml_singletons}
55)
56\endcode
57
58\note set_source_files_properties needs to be called before \c{qt_add_qml_module}
59
60\section3 Without qt_add_qml_module
61If you aren't using \c{qt_add_qml_module}, you'll need to manually create a
62\l{Module Definition qmldir Files}{qmldir file}.
63There, you'll need to mark your singletons accordingly:
64\code [text]
65module MyModule
66singleton MySingleton 1.0 MySingleton.qml
67singleton MyOtherSingleton 1.0 MyOtherSingleton.qml
68\endcode
69See also \l{Object Type Declaration} for more details.
70
71
72\section2 Defining singletons in C++
73
74There are multiple ways of exposing singletons to QML from C++. The main
75difference depends on whether a new instance of a class should be created when
76needed by the QML engine; or if some existing object needs to be exposed to a
77QML program.
78
79\section3 Registering a class to provide singletons
80
81The simplest way of defining a singleton is to have a default-constructible
82class, which derives from QObject and mark it with the \l{QML_SINGLETON} and
83\l{QML_ELEMENT} macros.
84\code
85class MySingleton : public QObject
86{
87 Q_OBJECT
88 QML_SINGLETON
89 QML_ELEMENT
90public:
91 MySingleton(QObject *parent = nullptr) : QObject(parent) {
92 // ...
93 }
94};
95\endcode
96This will register the \c{MySingleton} class under the name \c{MySingleton} in
97the QML module to which the file belongs.
98If you want to expose it under a different name, you can use \l{QML_NAMED_ELEMENT}
99instead.
100
101If the class can't be made default-constructible, or if you need access to
102the \l{QQmlEngine} in which the singleton is instantiated, it is possible to
103use a static create function instead. It must have the signature
104\c{MySingleton *create(QQmlEngine *, QJSEngine *)}, where \c{MySingleton} is
105the type of the class that gets registered.
106\code
107class MyNonDefaultConstructibleSingleton : public QObject
108{
109 Q_OBJECT
110 QML_SINGLETON
111 QML_NAMED_ELEMENT(MySingleton)
112public:
113 MyNonDefaultConstructibleSingleton(QJSValue id, QObject *parent = nullptr)
114 : QObject(parent)
115 , m_symbol(std::move(id))
116 {}
117
118 static MyNonDefaultConstructibleSingleton *create(QQmlEngine *qmlEngine, QJSEngine *)
119 {
120 return new MyNonDefaultConstructibleSingleton(qmlEngine->newSymbol(u"MySingleton"_s));
121 }
122
123private:
124 QJSValue m_symbol;
125};
126\endcode
127
128\note The create function takes both a \l{QJSEngine} and a \l{QQmlEngine} parameter. That is
129for historical reasons. They both point to the same object which is in fact a QQmlEngine.
130
131\section3 Exposing a C++-instantiated object as a singleton
132
133It can be useful to control the instantiation of a singleton from C++, instead
134of letting the \l{QQmlEngine} instantiate the instance. Such singletons do not need
135to be instantiatable by the engine, so you can forgo having a default constructor
136or a static \c{create} function. If you do, you must include the \l{QML_UNCREATABLE}
137macro in the singleton declaration:
138\code
139class MyNonDefaultConstructibleSingleton : public QObject
140{
141 Q_OBJECT
142 QML_SINGLETON
143 QML_NAMED_ELEMENT(MySingleton)
144 QML_UNCREATABLE("Provided by C++")
145public:
146 MyNonDefaultConstructibleSingleton(BackendObject* backend, QObject *parent = nullptr)
147 : QObject(parent)
148 , m_backend(backend)
149 {}
150
151private:
152 class BackendObject* backend;
153};
154\endcode
155
156Then, we set the instance to use on the engine, before we start it:
157
158\code
159MyNonDefaultConstructibleSingleton singleton(backend);
160QQmlApplicationEngine engine;
161engine.setExternalSingletonInstance("MyModule", "MySingleton", &singleton);
162engine.loadFromModule("MyModule", "Main");
163\endcode
164
165
166\section3 Exposing an existing object as a singleton
167
168Sometimes, you have an existing object that might have been created via
169some third-party API. Often, the right choice in this case is to have one
170singleton, which exposes those objects as its properties (see
171\l{Grouping together related data}).
172But if that is not the case, for example because there is only a single object that needs
173to be exposed, use the following approach to expose an instance of type
174\c{MySingleton} to the engine.
175We first expose the Singleton as a \l{QML_FOREIGN}{foreign type}:
176\code
177struct SingletonForeign
178{
179 Q_GADGET
180 QML_FOREIGN(MySingleton)
181 QML_SINGLETON
182 QML_NAMED_ELEMENT(MySingleton)
183 QML_UNCREATABLE("Provided from C++")
184};
185\endcode
186Then we simply instantiate the MySingleton, and set it to the engine:
187\code
188MySingleton instance = getSingletonInstance();
189QQmlApplicationEngine engine;
190engine.setExternalSingletonInstance("MyModule", "MySingleton", &instance);
191engine.loadFromModule("MyModule", "Main");
192\endcode
193
194\note It can be very tempting to simply use \l{qmlRegisterSingletonInstance} in
195this case. However, be wary of the pitfalls of imperative type registration
196listed in the next section.
197
198\section3 Imperative type registration
199Before Qt 5.15, all types, including singletons were registered via the
200\c{qmlRegisterType} API. Singletons specifically were registered via either
201\l{qmlRegisterSingletonType} or \l{qmlRegisterSingletonInstance}. Besides the
202minor annoyance of having to repeat the module name for each type and the forced
203decoupling of the class declaration and its registration, the major problem with
204that approach was that it is tooling unfriendly: It was not statically possible
205to extract all the necessary information about the types of a module at compile
206time. The declarative registration solved this issue.
207
208\note There is one remaining use case for the imperative \c{qmlRegisterType} API:
209It is a way to expose a singleton of non-QObject type as a \c{var} property via
210\l{qmlRegisterSingletonType}{the QJSValue based \c{qmlRegisterSingletonType} overload}
211. Prefer the alternative: Expose that value as the property of a (\c{QObject}) based
212singleton, so that type information will be available.
213
214\section2 Accessing singletons
215Singletons can be accessed both from QML as well as from C++. In QML, you need
216to import the containing module. Afterwards, you can access the singleton via its
217name. Reading its properties and writing to them inside JavaScript contexts is
218done in the same way as with normal objects:
219
220\qml
221import QtQuick
222import MyModule
223
224Item {
225 x: MySingleton.posX
226 Component.onCompleted: MySingleton.ready = true;
227}
228\endqml
229
230Setting up bindings on a singletons properties is not possible; however, if it
231is needed, a \l{Binding} element can be used to achieve the same result:
232\qml
233import QtQuick
234import MyModule
235
236Item {
237 id: root
238 Binding {
239 target: MySingleton
240 property: "posX"
241 value: root.x
242 }
243}
244\endqml
245
246\note Care must be taken when installing a binding on a singleton property: If
247done by more than one file, the results are not defined.
248
249\section1 Guidelines for (not) using singletons
250
251Singletons allow you to expose data which needs to be accessed in multiple places
252to the engine. That can be globally shared settings, like the spacing between
253elements, or data models which need to be displayed in multiple places.
254Compared to context properties which can solve a similar use case,
255they have the benefit of being typed, being supported by tooling like the
256\l{\QMLLS}, and they are also generally faster at runtime.
257
258It is recommended not to register too many singletons in a module: Singletons,
259once created, stay alive until the engine itself gets destroyed
260and come with the drawbacks of shared state as they are part of the global state.
261Thus consider using the following techniques to reduce the amount of singletons
262in your application:
263
264\section2 Grouping together related data
265Adding one singleton for each object which you want to expose adds quite some boiler plate.
266Most of the time, it makes more sense to group data you want to expose together as properties
267of a single singleton. Assume for instance that you want to create an ebook reader
268where you need to expose three \l{QAbstractItemModel}{abstract item models}, one
269for local books, and two for remote sources. Instead of repeating the process
270for \l{Exposing an existing object as a singleton}{exposing existing objects}
271three times, you can instead create one singleton and set it up before starting
272the main application:
273\code
274class GlobalState : QObject
275{
276 Q_OBJECT
277 QML_ELEMENT
278 QML_SINGLETON
279 Q_PROPERTY(QAbstractItemModel* localBooks MEMBER localBooks)
280 Q_PROPERTY(QAbstractItemModel* digitalStoreFront MEMBER digitalStoreFront)
281 Q_PROPERTY(QAbstractItemModel* publicLibrary MEMBER publicLibrary)
282public:
283 QAbstractItemModel* localBooks;
284 QAbstractItemModel* digitalStoreFront;
285 QAbstractItemModel* publicLibrary
286};
287
288int main() {
289 QQmlApplicationEngine engine;
290 auto globalState = engine.singletonInstance<GlobalState *>("MyModule", "GlobalState");
291 globalState->localBooks = getLocalBooks();
292 globalState->digitalStoreFront = setupLoalStoreFront();
293 globalState->publicLibrary = accessPublicLibrary();
294 engine.loadFromModule("MyModule", "Main");
295}
296\endcode
297
298\section2 Use object instances
299In the last section, we had the example of exposing three models as members of a
300singleton. That can be useful when either the models need to be used in multiple
301places, or when they are provided by some external API over which we have no
302control. However, if we need the models only in a single place it might make
303more sense have them as an instantiable type. Coming back to the previous example,
304we can add an instantiable RemoteBookModel class, and then instantiate it inside
305the book browser QML file:
306
307
308\code
309// remotebookmodel.h
310class RemoteBookModel : public QAbstractItemModel
311{
312 Q_OBJECT
313 QML_ELEMENT
314 Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
315 // ...
316};
317\endcode
318
319\qml
320// bookbrowser.qml
321Row {
322 ListView {
323 model: RemoteBookModel { url: "www.public-lib.example"}
324 }
325 ListView {
326 model: RemoteBookModel { url: "www.store-front.example"}
327 }
328}
329
330\endqml
331
332\section2 Passing initial state
333
334While singletons can be used to pass state to QML, they are wasteful when the
335state is only needed for the initial setup of the application. In that case, it
336is often possible to use \l{QQmlApplicationEngine::setInitialProperties}.
337You might for instance want to set \l{Window::visibility} to fullscreen if
338a corresponding command line flag has been set:
339\code
340QQmlApplicationEngine engine;
341if (parser.isSet(fullScreenOption)) {
342 // assumes root item is ApplicationWindow
343 engine.setInitialProperties(
344 { "visibility", QVariant::fromValue(QWindow::FullScreen)}
345 );
346}
347engine.loadFromModule("MyModule, "Main");
348\endcode
349
350*/