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
qtqml-writing-a-module.qdoc
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
3
4/*!
5\page qtqml-writing-a-module.html
6\title Writing QML Modules
7\brief How to write a custom QML module.
8
9You should declare a QML module using the \l{qt_add_qml_module}
10{CMake QML Module API} to:
11
12\list
13\li Generate \l {Module Definition qmldir Files}{qmldir} and
14 \l {Type Description Files}{*.qmltypes files}.
15\li Register C++ types annotated with \l QML_ELEMENT.
16\li Combine QML files and C++-based types in the same module.
17\li Invoke \l {Ahead-of-Time-Compilation}{qmlcachegen} on all
18 QML files.
19\li Use the pre-compiled versions of QML files inside the module.
20\li Provide the module both in the physical and in the
21 \l{The Qt Resource System}{resource file system}.
22\li Create a backing library and an optional plugin. Link the backing library
23 into the application to avoid loading the plugin at run time.
24\endlist
25
26All the above actions can also be configured separately.
27For more information, see \l {qt_add_qml_module}{CMake QML Module API}.
28
29\section1 Multiple QML Modules in One Binary
30
31You can add multiple QML modules into the same binary. Define a CMake target for
32each module and then link the targets to the executable.
33If the extra targets are all static libraries, the result will be one binary,
34which contains multiple QML modules. In short you can create an application
35like this:
36
37\code [text]
38myProject
39 ├── CMakeLists.txt
40 ├── main.cpp
41 ├── main.qml
42 ├── onething.h
43 ├── onething.cpp
44 └── ExtraModule
45 ├── CMakeLists.txt
46 ├── Extra.qml
47 ├── extrathing.h
48 └── extrathing.cpp
49\endcode
50
51To begin, let's assume main.qml contains an instantiation of Extra.qml:
52
53\code [qml]
54import ExtraModule
55
56Extra {
57 // ...
58}
59\endcode
60
61The extra module has to be a static library so that you can link it
62into the main program. Therefore, state as much in ExtraModule/CMakeLists.txt:
63
64\quotefromfile qml/CMakeLists.txt
65\printuntil extrathing.h
66\printuntil )
67
68This generates two targets: \c extra_module for the backing library, and
69\c extra_moduleplugin for the plugin. Being a static library too, the plugin cannot
70be loaded at runtime.
71
72In myProject/CMakeLists.txt you need to specify the QML module that main.qml
73and any types declared in onething.h are part of:
74
75\quotefromfile qml/myProject-CMakeLists.txt
76\printuntil onething.h
77\printuntil )
78
79
80From there, you add the subdirectory for the extra module:
81
82\quotefromfile qml/CMakeLists.txt
83\skipto add_subdirectory
84\printuntil )
85
86To ensure that linking the extra module works correctly, you need to:
87
88\list
89\li Define a symbol in the extra module.
90\li Create a reference to the symbol from the main program.
91\endlist
92
93QML plugins contain a symbol you can use for this purpose.
94You can use the \l Q_IMPORT_QML_PLUGIN macro to create a reference to this symbol.
95Add the following code to the main.cpp:
96
97\code
98#include <QtQml/QQmlExtensionPlugin>
99Q_IMPORT_QML_PLUGIN(ExtraModulePlugin)
100\endcode
101
102\c ExtraModulePlugin is the name of the generated plugin class. It's composed
103of the module URI with \c Plugin appended to it. Then, in the main program's
104CMakeLists.txt, link the plugin, not the backing library, into the main program:
105
106\quotefromfile qml/myProject-CMakeLists.txt
107\skipto target_link_libraries
108\printuntil )
109
110\section1 Versions
111
112QML has a complex system to assign versions to components and modules. In most
113cases you should ignore all of it by:
114
115\list 1
116 \li Never adding a version to your import statements
117 \li Never specifying any versions in \l{qt_add_qml_module}
118 \li Never using \l{QML_ADDED_IN_VERSION} or \l{QT_QML_SOURCE_VERSIONS}
119 \li Never using \l{Q_REVISION} or the \c{REVISION()} attribute in \l{Q_PROPERTY}
120 \li Avoiding unqualified access
121 \li Generously using import namespaces
122\endlist
123
124Versioning is ideally handled outside the language itself. You may, for example,
125keep separate \l{QML Import Path}{import paths} for different sets of QML modules.
126Or you may use a versioning mechanism provided by your operating system to install
127or uninstall packages with QML modules.
128
129In some cases, Qt's own QML modules may show different behavior, depending on what
130version is imported. In particular, if a property is added to a QML component, and
131your code contains unqualified access to another property of the same name, your
132code will break. In the following example, the code will behave differently
133depending on the version of Qt, because the \l [QML] {Rectangle::}{topLeftRadius}
134property was added in Qt 6.7:
135
136\qml
137import QtQuick
138
139Item {
140 // property you want to use
141 property real topLeftRadius: 24
142
143 Rectangle {
144
145 // correct for Qt version < 6.7 but uses Rectangle's topLeftRadius in 6.7
146 objectName: "top left radius:" + topLeftRadius
147 }
148}
149\endqml
150
151The solution here is to avoid the unqualified access. \l{qmllint} can be used to
152find such problems. The following example accesses the property you actually mean
153in a safe, qualified way:
154
155\qml
156import QtQuick
157
158Item {
159 id: root
160
161 // property you want to use
162 property real topLeftRadius: 24
163
164 Rectangle {
165
166 // never mixes up topLeftRadius with unrelated Rectangle's topLeftRadius
167 objectName: "top left radius:" + root.topLeftRadius
168 }
169}
170\endqml
171
172You can also avoid the incompatibility by importing a specific version of \l[QML]{QtQuick}:
173
174\qml
175// make sure Rectangle has no topLeftRadius property
176import QtQuick 6.6
177
178Item {
179 property real topLeftRadius: 24
180 Rectangle {
181 objectName: "top left radius:" + topLeftRadius
182 }
183}
184\endqml
185
186Another problem solved by versioning is the fact that QML components imported by
187different modules may shadow each other. In the following example, if \c{MyModule} were
188to introduce a component named \c{Rectangle} in a newer version, the \c{Rectangle}
189created by this document would not be a \c {QQuickRectangle} anymore, but rather the
190new \c{Rectangle} introduced by \c{MyModule}.
191
192\qml
193import QtQuick
194import MyModule
195
196Rectangle {
197 // MyModule's Rectangle, not QtQuick's
198}
199\endqml
200
201A good way to avoid the shadowing would be to import \c{QtQuick} and/or \c{MyModule}
202into type namespaces as follows:
203
204\qml
205import QtQuick as QQ
206import MyModule as MM
207
208QQ.Rectangle {
209 // QtQuick's Rectangle
210}
211\endqml
212
213Alternatively, if you import \c{MyModule} with a fixed version, and the new component
214receives a correct version tag via \l{QML_ADDED_IN_VERSION} or \l{QT_QML_SOURCE_VERSIONS},
215the shadowing is also avoided:
216
217\qml
218import QtQuick 6.6
219
220// Types introduced after 1.0 are not available, like Rectangle for example
221import MyModule 1.0
222
223Rectangle {
224 // QtQuick's Rectangle
225}
226\endqml
227
228For this to work, you need to use versions in \c{MyModule}. There are a few things
229to be aware of.
230
231\section2 If you add versions, add them everywhere
232
233You need to add a \c{VERSION} attribute to \l{qt_add_qml_module}. The version should
234be the most recent version provided by your module. Older minor versions of the same
235major version will automatically be registered. For older major versions, see
236\l{#Exporting Multiple Major Versions from The Same Module}{below}.
237
238You should add \l{QML_ADDED_IN_VERSION} or \l{QT_QML_SOURCE_VERSIONS} to every type
239that was \e not introduced in version \c{x.0} of your module, where \c{x} is the current
240major version.
241
242If you forget to add a version tag, the component will be available in all versions,
243making the versioning ineffective.
244
245However, there is no way to add versions to properties, methods, and signals defined
246in QML. The only way to version QML documents is to add a new document with separate
247\l{QT_QML_SOURCE_VERSIONS} for each change.
248
249\section2 Versions are not transitive
250
251If a component from your module \c{A} imports another module \c{B} and instantiates a type
252from that module as the root element, then the import version of \c{B} is relevant for the
253properties available from the resulting component, no matter what version of \c{A} is
254imported by a user.
255
256Consider a file \c{TypeFromA.qml} with version \c{2.6} in module \c{A}:
257
258\qml
259import B 2.7
260
261// Exposes TypeFromB 2.7, no matter what version of A is imported
262TypeFromB { }
263\endqml
264
265Now consider a user of \c{TypeFromA}:
266
267\qml
268import A 2.6
269
270// This is TypeFromB 2.7.
271TypeFromA { }
272\endqml
273
274The user hopes to see version \c{2.6} but actually gets version
275\c{2.7} of the base class \c{TypeFromB}.
276
277Therefore, in order to be safe, you not only have to duplicate your QML files and
278give them new versions when you add properties yourself, but also when you bump
279versions of modules you import.
280
281\section2 Qualified access does not honor versioning
282
283Versioning only affects unqualified access to members of a type or the type itself.
284In the example with \c{topLeftRadius}, if you write \c{this.topLeftRadius}, the
285property will be resolved if you're using Qt 6.7, even if you \c{import QtQuick 6.6}.
286
287\section2 Versions and revisions
288
289With \l{QML_ADDED_IN_VERSION}, and the two-argument variants of \l{Q_REVISION} and
290\l{Q_PROPERTY}'s \c{REVISION()}, you can only declare versions that are tightly coupled
291to the \l{QMetaObject}{metaobject's} revision as exposed in \l{QMetaMethod::revision}
292and \l{QMetaProperty::revision}. This means all the types in your type hierarchy have
293to follow the same versioning scheme. This includes any types provided by Qt itself
294that you inherit from.
295
296With \l qmlRegisterType and related functions you can register any mapping
297between metaobject revisions and type versions. You then need to use the one-argument forms
298of \l{Q_REVISION} and the \c{REVISION} attribute of \l{Q_PROPERTY}. However, this
299can become rather complex and confusing and is not recommended.
300
301\section2 Exporting multiple major versions from the same module
302
303\l qt_add_qml_module by default considers the major version given in its
304VERSION argument, even if the individual types declare other versions in their
305added specific version via \l QT_QML_SOURCE_VERSIONS or \l Q_REVISION.
306If a module is available under more than one version, you also need to decide
307what versions the individual QML files are available under. To declare further
308major versions, you can use the \c PAST_MAJOR_VERSIONS option to
309\c qt_add_qml_module as well as the \c {QT_QML_SOURCE_VERSIONS} property on
310individual QML files.
311
312\quotefile qml/MajorProject-CMakeLists.txt
313
314\c MyModule is available in major versions 1, 2, and 3. The maximum version
315available is 3.2. You can import any version 1.x or 2.x with a positive x. For
316Thing.qml and OtherThing.qml we have added explicit version information.
317Thing.qml is available from version 1.4, and OtherThing.qml is available from
318version 2.2. You have to specify the later versions, too, in each
319\c set_source_files_properties() because you may remove QML files
320from a module when bumping the major version. There is no explicit version
321information for OneMoreThing.qml. This means that OneMoreThing.qml is available
322in all major versions, from minor version 0.
323
324With this setup, the generated registration code will register the module
325\c versions using \l qmlRegisterModule() for each of the major versions. This
326way, all versions can be imported.
327
328
329\section1 Custom Directory Layouts
330
331The easiest way to structure QML modules is to keep them in directories named by
332their URIs. For example, a module My.Extra.Module would live in a directory
333My/Extra/Module relative to the application that uses it. This way, they can
334easily be found at run time and by any tools.
335
336In more complex projects, this convention can be too limiting. You might for
337instance want to group all QML modules in one place to avoid polluting the
338project's root directory. Or you want to reuse a single module in multiple
339applications. For those cases, \c QT_QML_OUTPUT_DIRECTORY in combination with
340\c RESOURCE_PREFIX and \l IMPORT_PATH can be used.
341
342To collect QML modules into a specific output directory, for example a
343subdirectory "qml" in the build directory \l QT_QML_OUTPUT_DIRECTORY, set the
344following in the top-level CMakeLists.txt:
345
346\code [cmake]
347set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml)
348\endcode
349
350The output directories of QML modules move to the new location.
351Likewise, the \c qmllint and \c qmlcachegen invocations are automatically
352adapted to use the new output directory as an \l[QtQml]{QML Import Path}{import path}.
353Because the new output directory is not part of the default QML import path,
354you have to add it explicitly at run time, so that the QML modules can be found.
355
356
357Now that the physical file system is taken care of, you may still want to move
358the QML modules into a different place in the resource file system. This is what
359the RESOURCE_PREFIX option is for. You have to specify it separately in
360each \l qt_add_qml_module. The QML module will then be placed under the specified
361prefix, with a target path generated from the URI appended. For example,
362consider the following module:
363
364\code
365qt_add_qml_module(
366 URI My.Great.Module
367 VERSION 1.0
368 RESOURCE_PREFIX /example.com/qml
369 QML_FILES
370 A.qml
371 B.qml
372)
373\endcode
374
375This will add a directory \c example.com/qml/My/Great/Module to the resource file
376system and place the QML module defined above in it. You don't strictly need to
377add the resource prefix to the QML import path as the module can still be found
378in the physical file system. However, it generally is a good idea to add the
379resource prefix to the QML import path because loading from the resource file
380system is faster than loading from the physical file system for most modules.
381
382If the QML modules are meant to be used in a larger project with multiple import
383paths, you'll have to do an additional step: Even if you add the import paths at
384run time, tooling like \c qmllint does not have access to it, and might fail to
385find the correct dependencies. Use \c IMPORT_PATH to tell tooling about the
386additional paths it has to consider. For example:
387
388\code [cmake]
389qt_add_qml_module(
390 URI My.Dependent.Module
391 VERSION 1.0
392 QML_FILES
393 C.qml
394 IMPORT_PATH "/some/where/else"
395)
396\endcode
397
398\section1 Eliminating Run Time File System Access
399
400If all QML modules are always loaded from the resource
401file system, you can deploy the application as a single binary.
402
403If \l{QTP0001} policy is set to \c NEW, the \c RESOURCE_PREFIX argument
404for \c{qt_add_qml_module()} defaults to \c{/qt/qml/}, therefore your
405modules are placed in \c{:/qt/qml/} in the resource file system.
406This is part of the default \l{QML Import Path}, but not used by Qt
407itself. For modules to be used within your application, this is the right place.
408
409If you have instead specified a custom \c RESOURCE_PREFIX, you have to add the
410custom resource prefix to the \l{QML Import Path}. You can also add multiple
411resource prefixes:
412
413\code
414QQmlEngine qmlEngine;
415qmlEngine.addImportPath(QStringLiteral(":/my/resource/prefix"));
416qmlEngine.addImportPath(QStringLiteral(":/other/resource/prefix"));
417// Use qmlEngine to load the main.qml file.
418\endcode
419
420This might be necessary when using third party libraries to avoid module name
421conflicts. Using a custom resource prefix is discouraged in all other cases.
422
423The path \c :/qt-project.org/imports/ is also part of the default \l{QML Import
424Path}. For modules that are heavily re-used across different projects or Qt
425versions, \c :/qt-project.org/imports/ is acceptable as resource prefix. Qt's
426own QML modules are placed there, though. You have to be careful not to
427overwrite them.
428
429Do not add any unnecessary import paths. The QML engine might find your modules
430in the wrong place then. This can trigger problems which can only be reproduced
431in specific environments.
432
433\section1 Integrating custom QML plugins
434
435If you bundle an \l {QQuickImageProvider}{image provider} in the QML module, you
436need to implement the \l {QQmlEngineExtensionPlugin::initializeEngine()}
437method. This, in turn, makes it necessary to write your own plugin. To support
438this use case, \l NO_GENERATE_PLUGIN_SOURCE can be used.
439
440Let's consider a module that provides its own plugin source:
441
442\quotefile qml/myimageprovider.txt
443
444You may declare an image provider in myimageprovider.h, like this:
445
446\code
447class MyImageProvider : public QQuickImageProvider
448{
449 // ...
450};
451\endcode
452
453In plugin.cpp you can then define the QQmlEngineExtensionPlugin:
454
455\quotefile qml/plugin.cpp.txt
456
457This will make the image provider available. The plugin and the backing library
458both are in the same CMake target imageproviderplugin. This is done so that the
459linker does not drop parts of the module in various scenarios.
460
461As the plugin creates an image provider, it no longer has a trivial
462\c initializeEngine function. Therefore, the plugin is no longer optional.
463
464\sa {Changes to Qt Qml}, {Modernizing QML Modules}, {Port QML modules to CMake}
465
466*/