应该声明 QML 模块使用 CMake QML 模块 API to:
All the above actions can also be configured separately. For more information, see CMake QML 模块 API .
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:
myProject | - CMakeLists.txt | - main.cpp | - main.qml | - onething.h | - onething.cpp | - ExtraModule | - CMakeLists.txt | - Extra.qml | - extrathing.h | - extrathing.cpp
To begin, let's assume main.qml contains an instantiation of Extra.qml:
import ExtraModule Extra { ... }
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:
# Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause qt_add_library(extra_module STATIC) qt_add_qml_module(extra_module URI "ExtraModule" VERSION 1.0 QML_FILES Extra.qml SOURCES extrathing.cpp extrathing.h RESOURCE_PREFIX / )
This generates two targets:
extra_module
for the backing library, and
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:
qt_add_executable(main_program main.cpp) qt_add_qml_module(main_program VERSION 1.0 URI myProject QML_FILES main.qml SOURCES onething.cpp onething.h )
From there, you add the subdirectory for the extra module:
add_subdirectory(ExtraModule)
To ensure that linking the extra module works correctly, you need to:
QML plugins contain a symbol you can use for this purpose. You can use the Q_IMPORT_QML_PLUGIN macro to create a reference to this symbol. Add the following code to the main.cpp:
#include <QtQml/QQmlExtensionPlugin> Q_IMPORT_QML_PLUGIN(ExtraModulePlugin)
ExtraModulePlugin
is the name of the generated plugin class. It's composed of the module URI with
Plugin
appended to it. Then, in the main program's CMakeLists.txt, link the plugin, not the backing library, into the main program:
target_link_libraries(main_program PRIVATE extra_moduleplugin)
QML has a complex system to assign versions to components and modules. In most cases you should ignore all of it by:
REVISION()
attribute in
Q_PROPERTY
Versioning is ideally handled outside the language itself. You may, for example, keep separate 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 topLeftRadius property was added in Qt 6.7:
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 } }
The solution here is to avoid the unqualified access. qmllint can be used to find such problems. The following example accesses the property you actually mean in a safe, qualified way:
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 } }
You can also avoid the incompatibility by importing a specific version of QtQuick :
// make sure Rectangle has no topLeftRadius property import QtQuick 6.6 Item { property real topLeftRadius: 24 Rectangle { objectName: "top left radius:" + topLeftRadius } }
Another problem solved by versioning is the fact that QML components imported by different modules may shadow each other. In the following example, if
MyModule
were to introduce a component named
Rectangle
in a newer version, the
Rectangle
created by this document would not be a
QQuickRectangle
anymore, but rather the new
Rectangle
introduced by
MyModule
.
import QtQuick import MyModule Rectangle { // MyModule's Rectangle, not QtQuick's }
A good way to avoid the shadowing would be to import
QtQuick
and/or
MyModule
into type namespaces as follows:
import QtQuick as QQ import MyModule as MM QQ.Rectangle { // QtQuick's Rectangle }
Alternatively, if you import
MyModule
with a fixed version, and the new component receives a correct version tag via
QML_ADDED_IN_VERSION
or
QT_QML_SOURCE_VERSIONS
, the shadowing is also avoided:
import QtQuick 6.6 // Types introduced after 1.0 are not available, like Rectangle for example import MyModule 1.0 Rectangle { // QtQuick's Rectangle }
For this to work, you need to use versions in
MyModule
. There are a few things to be aware of.
You need to add a
VERSION
attribute to
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
below
.
You should add
QML_ADDED_IN_VERSION
or
QT_QML_SOURCE_VERSIONS
to every type that was
not
introduced in version
x.0
of your module, where
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 QT_QML_SOURCE_VERSIONS for each change.
If a component from your module
A
imports another module
B
and instantiates a type from that module as the root element, then the import version of
B
is relevant for the properties available from the resulting component, no matter what version of
A
is imported by a user.
Consider a file
TypeFromA.qml
with version
2.6
in module
A
:
import B 2.7 // Exposes TypeFromB 2.7, no matter what version of A is imported TypeFromB { }
Now consider a user of
TypeFromA
:
import A 2.6 // This is TypeFromB 2.7. TypeFromA { }
The user hopes to see version
2.6
but actually gets version
2.7
of the base class
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.
Versioning only affects unqualified access to members of a type or the type itself. In the example with
topLeftRadius
,若编写
this.topLeftRadius
, the property will be resolved if you're using Qt 6.7, even if you
import QtQuick 6.6
.
采用
QML_ADDED_IN_VERSION
, and the two-argument variants of
Q_REVISION
and
Q_PROPERTY
's
REVISION()
, you can only declare versions that are tightly coupled to the
metaobject's
revision as exposed in
QMetaMethod::revision
and
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.
采用
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
Q_REVISION
和
REVISION
attribute of
Q_PROPERTY
. However, this can become rather complex and confusing and is not recommended.
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
QT_QML_SOURCE_VERSIONS
or
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
PAST_MAJOR_VERSIONS
选项到
qt_add_qml_module
as well as the
QT_QML_SOURCE_VERSIONS
property on individual QML files.
set_source_files_properties(Thing.qml PROPERTIES QT_QML_SOURCE_VERSIONS "1.4;2.0;3.0" ) set_source_files_properties(OtherThing.qml PROPERTIES QT_QML_SOURCE_VERSIONS "2.2;3.0" ) qt_add_qml_module(my_module URI MyModule VERSION 3.2 PAST_MAJOR_VERSIONS 1 2 QML_FILES Thing.qml OtherThing.qml OneMoreThing.qml SOURCES everything.cpp everything.h )
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
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
versions
使用
qmlRegisterModule
() for each of the major versions. This way, all versions can be imported.
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,
QT_QML_OUTPUT_DIRECTORY
in combination with
RESOURCE_PREFIX
and
IMPORT_PATH
可以使用。
To collect QML modules into a specific output directory, for example a subdirectory "qml" in the build directory QT_QML_OUTPUT_DIRECTORY , set the following in the top-level CMakeLists.txt:
set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml)
The output directories of QML modules move to the new location. Likewise, the
qmllint
and
qmlcachegen
invocations are automatically adapted to use the new output directory as an
import 路径
. 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 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:
qt_add_qml_module( URI My.Great.Module VERSION 1.0 RESOURCE_PREFIX /example.com/qml QML_FILES A.qml B.qml )
This will add a directory
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
qmllint
does not have access to it, and might fail to find the correct dependencies. Use
IMPORT_PATH
to tell tooling about the additional paths it has to consider. For example:
qt_add_qml_module( URI My.Dependent.Module VERSION 1.0 QML_FILES C.qml IMPORT_PATH "/some/where/else" )
If all QML modules are always loaded from the resource file system, you can deploy the application as a single binary.
若
QTP0001
policy is set to
NEW
,
RESOURCE_PREFIX
argument for
qt_add_qml_module()
默认为
/qt/qml/
, therefore your modules are placed in
:/qt/qml/
in the resource file system. This is part of the default
QML 导入路径
, 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
RESOURCE_PREFIX
, you have to add the custom resource prefix to the
QML 导入路径
. You can also add multiple resource prefixes:
QQmlEngine qmlEngine; qmlEngine.addImportPath(QStringLiteral(":/my/resource/prefix")); qmlEngine.addImportPath(QStringLiteral(":/other/resource/prefix")); // Use qmlEngine to load the main.qml file.
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
:/qt-project.org/imports/
is also part of the default
QML 导入路径
. For modules that are heavily re-used across different projects or Qt versions,
:/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.
若捆绑 图像提供程序 in the QML module, you need to implement the QQmlEngineExtensionPlugin::initializeEngine () method. This, in turn, makes it necessary to write your own plugin. To support this use case, NO_GENERATE_PLUGIN_SOURCE 可以使用。
Let's consider a module that provides its own plugin source:
qt_add_qml_module(imageproviderplugin VERSION 1.0 URI "ImageProvider" PLUGIN_TARGET imageproviderplugin NO_PLUGIN_OPTIONAL NO_GENERATE_PLUGIN_SOURCE CLASS_NAME ImageProviderExtensionPlugin QML_FILES AAA.qml BBB.qml SOURCES moretypes.cpp moretypes.h myimageprovider.cpp myimageprovider.h plugin.cpp )
You may declare an image provider in myimageprovider.h, like this:
class MyImageProvider : public QQuickImageProvider { [...] };
In plugin.cpp you can then define the QQmlEngineExtensionPlugin :
#include <myimageprovider.h> #include <QtQml/qqmlextensionplugin.h> class ImageProviderExtensionPlugin : public QQmlEngineExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID QQmlEngineExtensionInterface_iid) public: void initializeEngine(QQmlEngine *engine, const char *uri) final { Q_UNUSED(uri); engine->addImageProvider("myimg", new MyImageProvider); } };
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
initializeEngine
function. Therefore, the plugin is no longer optional.