diff options
Diffstat (limited to 'src/qml/doc/src/qtqml-writing-a-module.qdoc')
-rw-r--r-- | src/qml/doc/src/qtqml-writing-a-module.qdoc | 461 |
1 files changed, 461 insertions, 0 deletions
diff --git a/src/qml/doc/src/qtqml-writing-a-module.qdoc b/src/qml/doc/src/qtqml-writing-a-module.qdoc new file mode 100644 index 0000000000..c8a99f1a37 --- /dev/null +++ b/src/qml/doc/src/qtqml-writing-a-module.qdoc @@ -0,0 +1,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. + +*/ |