aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/doc/src/qtqml-writing-a-module.qdoc
blob: c8a99f1a371d8b06947ea8b90503b52c3bc60d20 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only

/*!
\page qtqml-writing-a-module.html
\title Writing QML Modules
\brief How to write a custom QML module.

You should declare a QML module using the \l{qt_add_qml_module}
{CMake QML Module API} to:

\list
\li Generate \l {Module Definition qmldir Files}{qmldir} and
    \l {Type Description Files}{*.qmltypes files}.
\li Register C++ types annotated with \l QML_ELEMENT.
\li Combine QML files and C++-based types in the same module.
\li Invoke \l {Ahead-of-Time-Compilation}{qmlcachegen} on all
    QML files.
\li Use the pre-compiled versions of QML files inside the module.
\li Provide the module both in the physical and in the
    \l{The Qt Resource System}{resource file system}.
\li Create a backing library and an optional plugin. Link the backing library
    into the application to avoid loading the plugin at run time.
\endlist

All the above actions can also be configured separately.
For more information, see \l {qt_add_qml_module}{CMake QML Module API}.

\section1 Multiple QML Modules in One Binary

You can add multiple QML modules into the same binary. Define a CMake target for
each module and then link the targets to the executable.
If the extra targets are all static libraries, the result will be one binary,
which contains multiple QML modules. In short you can create an application
like this:

\badcode
myProject
    | - CMakeLists.txt
    | - main.cpp
    | - main.qml
    | - onething.h
    | - onething.cpp
    | - ExtraModule
        | - CMakeLists.txt
        | - Extra.qml
        | - extrathing.h
        | - extrathing.cpp
\endcode

To begin, let's assume main.qml contains an instantiation of Extra.qml:

    \badcode
    import ExtraModule
    Extra { ... }
    \endcode

The extra module has to be a static library so that you can link it
into the main program. Therefore, state as much in ExtraModule/CMakeLists.txt:

\quotefromfile qml/CMakeLists.txt
\printuntil extrathing.h
\printuntil )

This generates two targets: \c extra_module for the backing library, and
\c extra_moduleplugin for the plugin. Being a static library too, the plugin cannot
be loaded at runtime.

In myProject/CMakeLists.txt you need to specify the QML module that main.qml
and any types declared in onething.h are part of:

\quotefromfile qml/myProject-CMakeLists.txt
\printuntil onething.h
\printuntil )


From there, you add the subdirectory for the extra module:

\quotefromfile qml/CMakeLists.txt
\skipto add_subdirectory
\printuntil )

To ensure that linking the extra module works correctly, you need to:

\list
\li Define a symbol in the extra module.
\li Create a reference to the symbol from the main program.
\endlist

QML plugins contain a symbol you can use for this purpose.
You can use the \l Q_IMPORT_QML_PLUGIN macro to create a reference to this symbol.
Add the following code to the main.cpp:

\badcode
#include <QtQml/QQmlExtensionPlugin>
Q_IMPORT_QML_PLUGIN(ExtraModulePlugin)
\endcode

\c ExtraModulePlugin is the name of the generated plugin class. It's composed
of the module URI with \c Plugin appended to it. Then, in the main program's
CMakeLists.txt, link the plugin, not the backing library, into the main program:

\quotefromfile qml/myProject-CMakeLists.txt
\skipto target_link_libraries
\printuntil )

\section1 Versions

QML has a complex system to assign versions to components and modules. In most
cases you should ignore all of it by:

\list 1
    \li Never adding a version to your import statements
    \li Never specifying any versions in \l{qt_add_qml_module}
    \li Never using \l{QML_ADDED_IN_VERSION} or \l{QT_QML_SOURCE_VERSIONS}
    \li Never using \l{Q_REVISION} or the \c{REVISION()} attribute in \l{Q_PROPERTY}
    \li Avoiding unqualified access
    \li Generously using import namespaces
\endlist

Versioning is ideally handled outside the language itself. You may, for example,
keep separate \l{QML Import Path}{import paths} for different sets of QML modules.
Or you may use a versioning mechanism provided by your operating system to install
or uninstall packages with QML modules.

In some cases, Qt's own QML modules may show different behavior, depending on what
version is imported. In particular, if a property is added to a QML component, and
your code contains unqualified access to another property of the same name, your
code will break. In the following example, the code will behave differently
depending on the version of Qt, because the \l [QML] {Rectangle::}{topLeftRadius}
property was added in Qt 6.7:

\qml
import QtQuick

Item {
    // property you want to use
    property real topLeftRadius: 24

    Rectangle {

        // correct for Qt version < 6.7 but uses Rectangle's topLeftRadius in 6.7
        objectName: "top left radius:" + topLeftRadius
    }
}
\endqml

The solution here is to avoid the unqualified access. \l{qmllint Reference}{qmllint} can be used to
find such problems. The following example accesses the property you actually mean
in a safe, qualified way:

\qml
import QtQuick

Item {
    id: root

    // property you want to use
    property real topLeftRadius: 24

    Rectangle {

        // never mixes up topLeftRadius with unrelated Rectangle's topLeftRadius
        objectName: "top left radius:" + root.topLeftRadius
    }
}
\endqml

You can also avoid the incompatibility by importing a specific version of QtQuick:

\qml
// make sure Rectangle has no topLeftRadius property
import QtQuick 6.6

Item {
    property real topLeftRadius: 24
    Rectangle {
        objectName: "top left radius:" + topLeftRadius
    }
}
\endqml

Another problem solved by versioning is the fact that QML components imported by
different modules may shadow each other. In the following example, if \c{MyModule} were
to introduce a component named \c{Rectangle} in a newer version, the \c{Rectangle}
created by this document would not be a \c {QQuickRectangle} anymore, but rather the
new \c{Rectangle} introduced by \c{MyModule}.

\qml
import QtQuick
import MyModule

Rectangle {
    // MyModule's Rectangle, not QtQuick's
}
\endqml

A good way to avoid the shadowing would be to import \c{QtQuick} and/or \c{MyModule}
into type namespaces as follows:

\qml
import QtQuick as QQ
import MyModule as MM

QQ.Rectangle {
   // QtQuick's Rectangle
}
\endqml

Alternatively, if you import \c{MyModule} with a fixed version, and the new component
receives a correct version tag via \l{QML_ADDED_IN_VERSION} or \l{QT_QML_SOURCE_VERSIONS},
the shadowing is also avoided:

\qml
import QtQuick 6.6

// Types introduced after 1.0 are not available, like Rectangle for example
import MyModule 1.0

Rectangle {
    // QtQuick's Rectangle
}
\endqml

For this to work, you need to use versions in \c{MyModule}. There are a few things
to be aware of.

\section2 If you add versions, add them everywhere

You need to add a \c{VERSION} attribute to \l{qt_add_qml_module}. The version should
be the most recent version provided by your module. Older minor versions of the same
major version will automatically be registered. For older major versions, see
\l{#Exporting Multiple Major Versions from The Same Module}{below}.

You should add \l{QML_ADDED_IN_VERSION} or \l{QT_QML_SOURCE_VERSIONS} to every type
that was \e not introduced in version \c{x.0} of your module, where \c{x} is the current
major version.

If you forget to add a version tag, the component will be available in all versions,
making the versioning ineffective.

However, there is no way to add versions to properties, methods, and signals defined
in QML. The only way to version QML documents is to add a new document with separate
\l{QT_QML_SOURCE_VERSIONS} for each change.

\section2 Versions are not transitive

If a component from your module \c{A} imports another module \c{B} and instantiates a type
from that module as the root element, then the import version of \c{B} is relevant for the
properties available from the resulting component, no matter what version of \c{A} is
imported by a user.

Consider a file \c{TypeFromA.qml} with version \c{2.6} in module \c{A}:

\qml
import B 2.7

// Exposes TypeFromB 2.7, no matter what version of A is imported
TypeFromB { }
\endqml

Now consider a user of \c{TypeFromA}:

\qml
import A 2.6

// This is TypeFromB 2.7.
TypeFromA { }
\endqml

The user hopes to see version \c{2.6} but actually gets version
\c{2.7} of the base class \c{TypeFromB}.

Therefore, in order to be safe, you not only have to duplicate your QML files and
give them new versions when you add properties yourself, but also when you bump
versions of modules you import.

\section2 Qualified access does not honor versioning

Versioning only affects unqualified access to members of a type or the type itself.
In the example with \c{topLeftRadius}, if you write \c{this.topLeftRadius}, the
property will be resolved if you're using Qt 6.7, even if you \c{import QtQuick 6.6}.

\section2 Versions and revisions

With \l{QML_ADDED_IN_VERSION}, and the two-argument variants of \l{Q_REVISION} and
\l{Q_PROPERTY}'s \c{REVISION()}, you can only declare versions that are tightly coupled
to the \l{QMetaObject}{metaobject's} revision as exposed in \l{QMetaMethod::revision}
and \l{QMetaProperty::revision}. This means all the types in your type hierarchy have
to follow the same versioning scheme. This includes any types provided by Qt itself
that you inherit from.

With \l{QQmlEngine::}{qmlRegisterType} and related functions you can register any mapping
between metaobject revisions and type versions. You then need to use the one-argument forms
of \l{Q_REVISION} and the \c{REVISION} attribute of \l{Q_PROPERTY}. However, this
can become rather complex and confusing and is not recommended.

\section2 Exporting multiple major versions from the same module

\l qt_add_qml_module by default considers the major version given in its
VERSION argument, even if the individual types declare other versions in their
added specific version via \l QT_QML_SOURCE_VERSIONS or \l Q_REVISION.
If a module is available under more than one version, you also need to decide
what versions the individual QML files are available under. To declare further
major versions, you can use the \c PAST_MAJOR_VERSIONS option to
\c qt_add_qml_module as well as the \c {QT_QML_SOURCE_VERSIONS} property on
individual QML files.

\quotefile qml/MajorProject-CMakeLists.txt

\c MyModule is available in major versions 1, 2, and 3. The maximum version
available is 3.2. You can import any version 1.x or 2.x with a positive x. For
Thing.qml and OtherThing.qml we have added explicit version information.
Thing.qml is available from version 1.4, and OtherThing.qml is available from
version 2.2. You have to specify the later versions, too, in each
\c set_source_files_properties() because you may remove QML files
from a module when bumping the major version. There is no explicit version
information for OneMoreThing.qml. This means that OneMoreThing.qml is available
in all major versions, from minor version 0.

With this setup, the generated registration code will register the module
\c versions using \l qmlRegisterModule() for each of the major versions. This
way, all versions can be imported.


\section1 Custom Directory Layouts

The easiest way to structure QML modules is to keep them in directories named by
their URIs. For example, a module My.Extra.Module would live in a directory
My/Extra/Module relative to the application that uses it. This way, they can
easily be found at run time and by any tools.

In more complex projects, this convention can be too limiting. You might for
instance want to group all QML modules in one place to avoid polluting the
project's root directory. Or you want to reuse a single module in multiple
applications. For those cases, \c QT_QML_OUTPUT_DIRECTORY in combination with
\c RESOURCE_PREFIX and \l IMPORT_PATH can be used.

To collect QML modules into a specific output directory, for example a
subdirectory "qml" in the build directory \l QT_QML_OUTPUT_DIRECTORY, set the
following in the top-level CMakeLists.txt:

\badcode
set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml)
\endcode

The output directories of QML modules move to the new location.
Likewise, the \c qmllint and \c qmlcachegen invocations are automatically
adapted to use the new output directory as an \l[QtQml]{QML Import Path}{import path}.
Because the new output directory is not part of the default QML import path,
you have to add it explicitly at run time, so that the QML modules can be found.


Now that the physical file system is taken care of, you may still want to move
the QML modules into a different place in the resource file system. This is what
the RESOURCE_PREFIX option is for. You have to specify it separately in
each \l qt_add_qml_module. The QML module will then be placed under the specified
prefix, with a target path generated from the URI appended. For example,
consider the following module:

\code
qt_add_qml_module(
    URI My.Great.Module
    VERSION 1.0
    RESOURCE_PREFIX /example.com/qml
    QML_FILES
        A.qml
        B.qml
)
\endcode

This will add a directory \c example.com/qml/My/Great/Module to the resource file
system and place the QML module defined above in it. You don't strictly need to
add the resource prefix to the QML import path as the module can still be found
in the physical file system. However, it generally is a good idea to add the
resource prefix to the QML import path because loading from the resource file
system is faster than loading from the physical file system for most modules.

If the QML modules are meant to be used in a larger project with multiple import
paths, you'll have to do an additional step: Even if you add the import paths at
run time, tooling like \c qmllint does not have access to it, and might fail to
find the correct dependencies. Use \c IMPORT_PATH to tell tooling about the
additional paths it has to consider. For example:

\badcode
qt_add_qml_module(
    URI My.Dependent.Module
    VERSION 1.0
    QML_FILES
        C.qml
    IMPORT_PATH "/some/where/else"
)
\endcode

\section1 Eliminating Run Time File System Access

If all QML modules are always loaded from the resource
file system, you can deploy the application as a single binary.

If \l{QTP0001} policy is set to \c NEW, the \c RESOURCE_PREFIX argument
for \c{qt_add_qml_module()} defaults to \c{/qt/qml/}, therefore your
modules are placed in \c{:/qt/qml/} in the resource file system.
This is part of the default \l{QML Import Path}, but not used by Qt
itself. For modules to be used within your application, this is the right place.

If you have instead specified a custom \c RESOURCE_PREFIX, you have to add the
custom resource prefix to the \l{QML Import Path}. You can also add multiple
resource prefixes:

\badcode
QQmlEngine qmlEngine;
qmlEngine.addImportPath(QStringLiteral(":/my/resource/prefix"));
qmlEngine.addImportPath(QStringLiteral(":/other/resource/prefix"));
// Use qmlEngine to load the main.qml file.
\endcode

This might be necessary when using third party libraries to avoid module name
conflicts. Using a custom resource prefix is discouraged in all other cases.

The path \c :/qt-project.org/imports/ is also part of the default \l{QML Import
Path}. For modules that are heavily re-used across different projects or Qt
versions, \c :/qt-project.org/imports/ is acceptable as resource prefix. Qt's
own QML modules are placed there, though. You have to be careful not to
overwrite them.

Do not add any unnecessary import paths. The QML engine might find your modules
in the wrong place then. This can trigger problems which can only be reproduced
in specific environments.

\section1 Integrating custom QML plugins

If you bundle an \l {QQuickImageProvider}{image provider} in the QML module, you
need to implement the \l {QQmlEngineExtensionPlugin::initializeEngine()}
method. This, in turn, makes it necessary to write your own plugin. To support
this use case, \l NO_GENERATE_PLUGIN_SOURCE can be used.

Let's consider a module that provides its own plugin source:

\quotefile qml/myimageprovider.txt

You may declare an image provider in myimageprovider.h, like this:

\badcode
class MyImageProvider : public QQuickImageProvider
{
    [...]
};
\endcode

In plugin.cpp you can then define the QQmlEngineExtensionPlugin:

\quotefile qml/plugin.cpp.txt

This will make the image provider available. The plugin and the backing library
both are in the same CMake target imageproviderplugin. This is done so that the
linker does not drop parts of the module in various scenarios.

As the plugin creates an image provider, it no longer has a trivial
\c initializeEngine function. Therefore, the plugin is no longer optional.

*/