aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/doc/src/qtqml-writing-a-module.qdoc
diff options
context:
space:
mode:
Diffstat (limited to 'src/qml/doc/src/qtqml-writing-a-module.qdoc')
-rw-r--r--src/qml/doc/src/qtqml-writing-a-module.qdoc461
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.
+
+*/