Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
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
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
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 an existing object as a singleton
132
133Sometimes, you have an existing object that might have been created via
134some third-party API. Often, the right choice in this case is to have one
135singleton, which exposes those objects as its properties (see
136\l{Grouping together related data}).
137But if that is not the case, for example because there is only a single object that needs
138to be exposed, use the following approach to expose an instance of type
139\c{MySingleton} to the engine.
140We first expose the Singleton as a \l{QML_FOREIGN}{foreign type}:
141\code
142struct SingletonForeign
143{
144 Q_GADGET
145 QML_FOREIGN(MySingleton)
146 QML_SINGLETON
147 QML_NAMED_ELEMENT(MySingleton)
148public:
149
150 inline static MySingleton *s_singletonInstance = nullptr;
151
152 static MySingleton *create(QQmlEngine *, QJSEngine *engine)
153 {
154 // The instance has to exist before it is used. We cannot replace it.
155 Q_ASSERT(s_singletonInstance);
156
157 // The engine has to have the same thread affinity as the singleton.
158 Q_ASSERT(engine->thread() == s_singletonInstance->thread());
159
160 // There can only be one engine accessing the singleton.
161 if (s_engine)
162 Q_ASSERT(engine == s_engine);
163 else
164 s_engine = engine;
165
166 // Explicitly specify C++ ownership so that the engine doesn't delete
167 // the instance.
168 QJSEngine::setObjectOwnership(s_singletonInstance,
169 QJSEngine::CppOwnership);
170 return s_singletonInstance;
171 }
172
173private:
174 inline static QJSEngine *s_engine = nullptr;
175};
176\endcode
177Then we set \c{SingletonForeign::s_singletonInstance} before we start
178the first engine
179\code
180SingletonForeign::s_singletonInstance = getSingletonInstance();
181QQmlApplicationEngine engine;
182engine.loadFromModule("MyModule", "Main");
183\endcode
184
185\note It can be very tempting to simply use \l{qmlRegisterSingletonInstance} in
186this case. However, be wary of the pitfalls of imperative type registration
187listed in the next section.
188
189\section3 Imperative type registration
190Before Qt 5.15, all types, including singletons were registered via the
191\c{qmlRegisterType} API. Singletons specifically were registered via either
192\l{qmlRegisterSingletonType} or \l{qmlRegisterSingletonInstance}. Besides the
193minor annoyance of having to repeat the module name for each type and the forced
194decoupling of the class declaration and its registration, the major problem with
195that approach was that it is tooling unfriendly: It was not statically possible
196to extract all the necessary information about the types of a module at compile
197time. The declarative registration solved this issue.
198
199\note There is one remaining use case for the imperative \c{qmlRegisterType} API:
200It is a way to expose a singleton of non-QObject type as a \c{var} property via
201\l{qmlRegisterSingletonType}{the QJSValue based \c{qmlRegisterSingletonType} overload}
202. Prefer the alternative: Expose that value as the property of a (\c{QObject}) based
203singleton, so that type information will be available.
204
205\section2 Accessing singletons
206Singletons can be accessed both from QML as well as from C++. In QML, you need
207to import the containing module. Afterwards, you can access the singleton via its
208name. Reading its properties and writing to them inside JavaScript contexts is
209done in the same way as with normal objects:
210
211\code
212import QtQuick
213import MyModule
214
215Item {
216 x: MySingleton.posX
217 Component.onCompleted: MySingleton.ready = true;
218}
219\endcode
220
221Setting up bindings on a singletons properties is not possible; however, if it
222is needed, a \l{Binding} element can be used to achieve the same result:
223\code
224import QtQuick
225import MyModule
226
227Item {
228 id: root
229 Binding {
230 target: MySingleton
231 property: "posX"
232 value: root.x
233 }
234}
235\endcode
236
237\note Care must be taken when installing a binding on a singleton property: If
238done by more than one file, the results are not defined.
239
240\section1 Guidelines for (not) using singletons
241
242Singletons allow you to expose data which needs to be accessed in multiple places
243to the engine. That can be globally shared settings, like the spacing between
244elements, or data models which need to be displayed in multiple places.
245Compared to context properties which can solve a similar use case,
246they have the benefit of being typed, being supported by tooling like the
247\l{\QMLLS Reference}{\QMLLS}, and they are also generally faster at runtime.
248
249It is recommended not to register too many singletons in a module: Singletons,
250once created, stay alive until the engine itself gets destroyed
251and come with the drawbacks of shared state as they are part of the global state.
252Thus consider using the following techniques to reduce the amount of singletons
253in your application:
254
255\section2 Grouping together related data
256Adding one singleton for each object which you want to expose adds quite some boiler plate.
257Most of the time, it makes more sense to group data you want to expose together as properties
258of a single singleton. Assume for instance that you want to create an ebook reader
259where you need to expose three \l{QAbstractItemModel}{abstract item models}, one
260for local books, and two for remote sources. Instead of repeating the process
261for \l{Exposing an existing object as a singleton}{exposing existing objects}
262three times, you can instead create one singleton and set it up before starting
263the main application:
264\code
265class GlobalState : QObject
266{
267 Q_OBJECT
268 QML_ELEMENT
269 QML_SINGLETON
270 Q_PROPERTY(QAbstractItemModel* localBooks MEMBER localBooks)
271 Q_PROPERTY(QAbstractItemModel* digitalStoreFront MEMBER digitalStoreFront)
272 Q_PROPERTY(QAbstractItemModel* publicLibrary MEMBER publicLibrary)
273public:
274 QAbstractItemModel* localBooks;
275 QAbstractItemModel* digitalStoreFront;
276 QAbstractItemModel* publicLibrary
277};
278
279int main() {
280 QQmlApplicationEngine engine;
281 auto globalState = engine.singletonInstance<GlobalState *>("MyModule", "GlobalState");
282 globalState->localBooks = getLocalBooks();
283 globalState->digitalStoreFront = setupLoalStoreFront();
284 globalState->publicLibrary = accessPublicLibrary();
285 engine.loadFromModule("MyModule", "Main");
286}
287\endcode
288
289\section2 Use object instances
290In the last section, we had the example of exposing three models as members of a
291singleton. That can be useful when either the models need to be used in multiple
292places, or when they are provided by some external API over which we have no
293control. However, if we need the models only in a single place it might make
294more sense have them as an instantiable type. Coming back to the previous example,
295we can add an instantiable RemoteBookModel class, and then instantiate it inside
296the book browser QML file:
297
298
299\code
300// remotebookmodel.h
301class RemoteBookModel : public QAbstractItemModel
302{
303 Q_OBJECT
304 QML_ELEMENT
305 Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
306 // ...
307};
308
309// bookbrowser.qml
310Row {
311 ListView {
312 model: RemoteBookModel { url: "www.public-lib.example"}
313 }
314 ListView {
315 model: RemoteBookModel { url: "www.store-front.example"}
316 }
317}
318
319\endcode
320
321\section2 Passing initial state
322
323While singletons can be used to pass state to QML, they are wasteful when the
324state is only needed for the initial setup of the application. In that case, it
325is often possible to use \l{QQmlApplicationEngine::setInitialProperties}.
326You might for instance want to set \l{Window::visibility} to fullscreen if
327a corresponding command line flag has been set:
328\code
329QQmlApplicationEngine engine;
330if (parser.isSet(fullScreenOption)) {
331 // assumes root item is ApplicationWindow
332 engine.setInitialProperties(
333 { "visibility", QVariant::fromValue(QWindow::FullScreen)}
334 );
335}
336engine.loadFromModule("MyModule, "Main");
337\endcode
338
339*/