Écriture de modules QML
Vous devez déclarer un module QML à l'aide de l'API CMake pour les modules QML afin de :
- Générer les fichiers qmldir et *.qmltypes.
- Enregistrer les types C++ annotés avec ` QML_ELEMENT`.
- Combiner des fichiers QML et des types basés sur C++ dans un même module.
- Appeler qmlcachegen sur tous les fichiers QML.
- Utiliser les versions précompilées des fichiers QML au sein du module.
- Fournir le module à la fois dans le système de fichiers physique et dans le système de fichiers de ressources.
- Créer une bibliothèque de support et un plugin facultatif. Lier la bibliothèque de support à l’application pour éviter de charger le plugin lors de l’exécution.
Toutes les actions ci-dessus peuvent également être configurées séparément. Pour plus d’informations, consultez l’API du module QML de CMake.
Plusieurs modules QML dans un seul binaire
Vous pouvez ajouter plusieurs modules QML dans un même binaire. Définissez une cible CMake pour chaque module, puis liez ces cibles à l’exécutable. Si les cibles supplémentaires sont toutes des bibliothèques statiques, le résultat sera un binaire unique contenant plusieurs modules QML. En résumé, vous pouvez créer une application comme celle-ci :
myProject
| - CMakeLists.txt
| - main.cpp
| - main.qml
| - onething.h
| - onething.cpp
| - ExtraModule
| - CMakeLists.txt
| - Extra.qml
| - extrathing.h
| - extrathing.cppPour commencer, supposons que `main.qml` contienne une instanciation de `Extra.qml` :
import ExtraModule
Extra { ... }Le module supplémentaire doit être une bibliothèque statique afin que vous puissiez le lier au programme principal. Indiquez-le donc dans 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 /
)Cela génère deux cibles : extra_module pour la bibliothèque de base, et extra_moduleplugin pour le plugin. Étant lui aussi une bibliothèque statique, le plugin ne peut pas être chargé à l’exécution.
Dans myProject/CMakeLists.txt, vous devez spécifier le module QML dont font partie main.qml et tous les types déclarés dans onething.h :
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
)À partir de là, vous ajoutez le sous-répertoire du module supplémentaire :
add_subdirectory(ExtraModule)Pour vous assurer que la liaison du module supplémentaire fonctionne correctement, vous devez :
- Définir un symbole dans le module supplémentaire.
- Créer une référence à ce symbole depuis le programme principal.
Les plugins QML contiennent un symbole que vous pouvez utiliser à cette fin. Vous pouvez utiliser la macro ` Q_IMPORT_QML_PLUGIN ` pour créer une référence à ce symbole. Ajoutez le code suivant dans le fichier `main.cpp` :
#include <QtQml/QQmlExtensionPlugin>
Q_IMPORT_QML_PLUGIN(ExtraModulePlugin)ExtraModulePlugin est le nom de la classe de plugin générée. Il est composé de l’URI du module suivi de « Plugin ». Ensuite, dans le fichier CMakeLists.txt du programme principal, liez le plugin, et non la bibliothèque sous-jacente, au programme principal :
target_link_libraries(main_program PRIVATE extra_moduleplugin)Versions
QML dispose d’un système complexe pour attribuer des versions aux composants et aux modules. Dans la plupart des cas, vous devriez ignorer tout cela en :
- ne jamais ajouter de version à vos instructions d’importation
- Ne jamais spécifier de version dans ` qt_add_qml_module`
- N’utilisez jamais QML_ADDED_IN_VERSION ni QT_QML_SOURCE_VERSIONS
- N’utilisez jamais ` Q_REVISION ` ni l’attribut `
REVISION()` dans Q_PROPERTY - Éviter les accès non qualifiés
- Utiliser généreusement les espaces de noms d’importation
La gestion des versions est idéalement effectuée en dehors du langage lui-même. Vous pouvez, par exemple, conserver des chemins d’importation distincts pour différents ensembles de modules QML. Vous pouvez également utiliser un mécanisme de gestion des versions fourni par votre système d’exploitation pour installer ou désinstaller des paquets contenant des modules QML.
Dans certains cas, les modules Qml propres à Qt peuvent présenter un comportement différent selon la version importée. En particulier, si une propriété est ajoutée à un composant Qml et que votre code contient un accès non qualifié à une autre propriété du même nom, votre code cessera de fonctionner. Dans l’exemple suivant, le code se comportera différemment selon la version de Qt, car la propriété ` topLeftRadius ` a été ajoutée dans 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
}
}La solution consiste ici à éviter l’accès non qualifié. qmllint peut être utilisé pour détecter ce type de problèmes. L’exemple suivant accède à la propriété souhaitée de manière sûre et qualifiée :
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
}
}Vous pouvez également éviter cette incompatibilité en important une version spécifique de ` QtQuick` :
// make sure Rectangle has no topLeftRadius property
import QtQuick 6.6
Item {
property real topLeftRadius: 24
Rectangle {
objectName: "top left radius:" + topLeftRadius
}
}Un autre problème résolu par la gestion des versions est le fait que les composants QML importés par différents modules peuvent se masquer mutuellement. Dans l’exemple suivant, si MyModule venait à introduire un composant nommé Rectangle dans une version plus récente, le Rectangle créé par ce document ne serait plus un QQuickRectangle, mais plutôt le nouveau Rectangle introduit par MyModule.
import QtQuick
import MyModule
Rectangle {
// MyModule's Rectangle, not QtQuick's
}Un bon moyen d’éviter ce masquage serait d’importer QtQuick et/ou MyModule dans des espaces de noms de types comme suit :
import QtQuick as QQ
import MyModule as MM
QQ.Rectangle {
// QtQuick's Rectangle
}Sinon, si vous importez MyModule avec une version fixe, et que le nouveau composant reçoit une balise de version correcte via QML_ADDED_IN_VERSION ou QT_QML_SOURCE_VERSIONS, le masquage est également évité :
import QtQuick 6.6
// Types introduced after 1.0 are not available, like Rectangle for example
import MyModule 1.0
Rectangle {
// QtQuick's Rectangle
}Pour que cela fonctionne, vous devez utiliser des versions dans MyModule. Il y a quelques points à prendre en compte.
Si vous ajoutez des versions, ajoutez-les partout
Vous devez ajouter un attribut ` VERSION ` à ` qt_add_qml_module`. La version doit correspondre à la version la plus récente fournie par votre module. Les versions mineures antérieures de la même version majeure seront automatiquement enregistrées. Pour les versions majeures antérieures, voir ci-dessous.
Vous devez ajouter QML_ADDED_IN_VERSION ou QT_QML_SOURCE_VERSIONS à chaque type qui n’ a pas été introduit dans la version x.0 de votre module, où x correspond à la version majeure actuelle.
Si vous oubliez d’ajouter une balise de version, le composant sera disponible dans toutes les versions, ce qui rendra la gestion des versions inefficace.
Cependant, il n’existe aucun moyen d’ajouter des versions aux propriétés, méthodes et signaux définis en QML. La seule façon de gérer les versions des documents QML consiste à créer un nouveau document avec une balise QT_QML_SOURCE_VERSIONS distincte pour chaque modification.
Les versions ne sont pas transitives
Si un composant de votre module A importe un autre module B et instancie un type de ce module en tant qu’élément racine, alors la version d’importation de B s’applique aux propriétés disponibles dans le composant résultant, quelle que soit la version de A importée par un utilisateur.
Considérons un fichier TypeFromA.qml avec la version 2.6 dans le module A:
import B 2.7
// Exposes TypeFromB 2.7, no matter what version of A is imported
TypeFromB { }Considérons maintenant un utilisateur de TypeFromA:
import A 2.6
// This is TypeFromB 2.7.
TypeFromA { }L'utilisateur s'attend à voir la version 2.6, mais obtient en réalité la version 2.7 de la classe de base TypeFromB.
Par conséquent, par mesure de sécurité, vous devez non seulement dupliquer vos fichiers QML et leur attribuer de nouvelles versions lorsque vous ajoutez vous-même des propriétés, mais également lorsque vous mettez à jour les versions des modules que vous importez.
L’accès qualifié ne tient pas compte de la gestion des versions
Le versionnage n’affecte que l’accès non qualifié aux membres d’un type ou au type lui-même. Dans l’exemple avec topLeftRadius, si vous écrivez this.topLeftRadius, la propriété sera résolue si vous utilisez Qt 6.7, même si vous import QtQuick 6.6.
Versions et révisions
Avec ` QML_ADDED_IN_VERSION`, ainsi qu’avec les variantes à deux arguments de ` Q_REVISION ` et ` Q_PROPERTY` ( REVISION()), vous ne pouvez déclarer que des versions étroitement liées à la révision ` metaobject's `, telle qu’exposée dans ` QMetaMethod::revision ` et ` QMetaProperty::revision`. Cela signifie que tous les types de votre hiérarchie de types doivent suivre le même schéma de gestion des versions. Cela inclut tous les types fournis par Qt lui-même dont vous héritez.
Avec ` qmlRegisterType ` et les fonctions associées, vous pouvez enregistrer n’importe quel mappage entre les révisions de méta-objets et les versions de types. Vous devez alors utiliser les formes à un argument de ` Q_REVISION ` et l’attribut ` REVISION ` de ` Q_PROPERTY`. Cependant, cela peut s’avérer assez complexe et prêter à confusion ; cette approche n’est donc pas recommandée.
Exportation de plusieurs versions majeures à partir d’un même module
Par défaut,`qt_add_qml_module` prend en compte la version majeure indiquée dans son argument `VERSION`, même si les types individuels déclarent d’autres versions dans leur version spécifique ajoutée via ` QT_QML_SOURCE_VERSIONS ` ou ` Q_REVISION`. Si un module est disponible sous plusieurs versions, vous devez également déterminer sous quelles versions les fichiers QML individuels sont disponibles. Pour déclarer d’autres versions majeures, vous pouvez utiliser l’option « PAST_MAJOR_VERSIONS » de la fonction ` qt_add_qml_module `, ainsi que la propriété ` QT_QML_SOURCE_VERSIONS ` sur les fichiers QML individuels.
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 est disponible dans les versions majeures 1, 2 et 3. La version maximale disponible est la 3.2. Vous pouvez importer n’importe quelle version 1.x ou 2.x avec un x positif. Pour Thing.qml et OtherThing.qml, nous avons ajouté des informations explicites sur la version. Thing.qml est disponible à partir de la version 1.4, et OtherThing.qml à partir de la version 2.2. Vous devez également spécifier les versions les plus récentes dans chaque fichier ` set_source_files_properties() `, car vous pouvez supprimer des fichiers QML d’un module lors de l’augmentation de la version majeure. Il n’y a pas d’informations explicites sur la version pour OneMoreThing.qml. Cela signifie que OneMoreThing.qml est disponible dans toutes les versions majeures, à partir de la version mineure 0.
Avec cette configuration, le code d’enregistrement généré enregistrera le module versions à l’aide de ` qmlRegisterModule()` pour chacune des versions majeures. De cette manière, toutes les versions peuvent être importées.
Structures de répertoires personnalisées
La manière la plus simple de structurer les modules QML consiste à les conserver dans des répertoires nommés d’après leurs URI. Par exemple, un module My.Extra.Module serait placé dans un répertoire My/Extra/Module par rapport à l’application qui l’utilise. De cette façon, ils peuvent être facilement localisés lors de l’exécution et par n’importe quel outil.
Dans les projets plus complexes, cette convention peut s’avérer trop restrictive. Vous pouvez par exemple souhaiter regrouper tous les modules QML au même endroit pour éviter d’encombrer le répertoire racine du projet. Ou bien vous souhaitez réutiliser un même module dans plusieurs applications. Dans ces cas-là, il est possible d’utiliser QT_QML_OUTPUT_DIRECTORY en combinaison avec RESOURCE_PREFIX et IMPORT_PATH.
Pour regrouper les modules QML dans un répertoire de sortie spécifique, par exemple un sous-répertoire « qml » dans le répertoire de compilation QT_QML_OUTPUT_DIRECTORY, définissez ce qui suit dans le fichier CMakeLists.txt de niveau supérieur :
set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml)Les répertoires de sortie des modules QML sont déplacés vers le nouvel emplacement. De même, les appels à qmllint et qmlcachegen sont automatiquement adaptés pour utiliser le nouveau répertoire de sortie comme chemin d’importation. Comme le nouveau répertoire de sortie ne fait pas partie du chemin d’importation QML par défaut, vous devez l’ajouter explicitement au moment de l’exécution, afin que les modules QML puissent être trouvés.
Maintenant que le système de fichiers physique est pris en charge, vous souhaiterez peut-être déplacer les modules QML vers un autre emplacement dans le système de fichiers de ressources. C’est à cela que sert l’option RESOURCE_PREFIX. Vous devez la spécifier séparément dans chaque appel à `qt_add_qml_module`. Le module QML sera alors placé sous le préfixe spécifié, avec un chemin cible généré à partir de l’URI ajouté à la fin. Prenons par exemple le module suivant :
qt_add_qml_module(
URI My.Great.Module
VERSION 1.0
RESOURCE_PREFIX /example.com/qml
QML_FILES
A.qml
B.qml
)Cela ajoutera un répertoire example.com/qml/My/Great/Module au système de fichiers de ressources et y placera le module QML défini ci-dessus. Vous n’avez pas strictement besoin d’ajouter le préfixe de ressource au chemin d’importation QML, car le module reste accessible dans le système de fichiers physique. Cependant, il est généralement recommandé d’ajouter le préfixe de ressource au chemin d’importation QML, car le chargement à partir du système de fichiers de ressources est plus rapide que celui à partir du système de fichiers physique pour la plupart des modules.
Si les modules QML sont destinés à être utilisés dans un projet plus vaste comportant plusieurs chemins d’importation, vous devrez effectuer une étape supplémentaire : même si vous ajoutez les chemins d’importation au moment de l’exécution, les outils tels que qmllint n’y ont pas accès et risquent de ne pas trouver les dépendances correctes. Utilisez IMPORT_PATH pour indiquer aux outils les chemins supplémentaires qu’ils doivent prendre en compte. Par exemple :
qt_add_qml_module(
URI My.Dependent.Module
VERSION 1.0
QML_FILES
C.qml
IMPORT_PATH "/some/where/else"
)Suppression de l’accès au système de fichiers à l’exécution
Si tous les modules QML sont toujours chargés à partir du système de fichiers de ressources, vous pouvez déployer l’application sous la forme d’un binaire unique.
Si la politique QTP0001 est définie sur « NEW », l’argument « RESOURCE_PREFIX » de qt_add_qml_module() prend par défaut la valeur « /qt/qml/ » ; vos modules sont donc placés dans « :/qt/qml/ » dans le système de fichiers de ressources. Cet emplacement fait partie du chemin d’importation QML par défaut, mais n’est pas utilisé par Qt lui-même. C’est l’emplacement approprié pour les modules destinés à être utilisés au sein de votre application.
Si vous avez au contraire spécifié une ressource personnalisée RESOURCE_PREFIX, vous devez ajouter le préfixe de ressource personnalisé au chemin d’importation QML. Vous pouvez également ajouter plusieurs préfixes de ressources :
QQmlEngine qmlEngine;
qmlEngine.addImportPath(QStringLiteral(":/my/resource/prefix"));
qmlEngine.addImportPath(QStringLiteral(":/other/resource/prefix"));
// Use qmlEngine to load the main.qml file.Cela peut s’avérer nécessaire lors de l’utilisation de bibliothèques tierces afin d’éviter les conflits de noms de modules. L’utilisation d’un préfixe de ressource personnalisé est déconseillée dans tous les autres cas.
Le chemin :/qt-project.org/imports/ fait également partie du chemin d’importation QML par défaut. Pour les modules largement réutilisés dans différents projets ou différentes versions de Qt, :/qt-project.org/imports/ est un préfixe de ressource acceptable. Les modules QML propres à Qt y sont toutefois placés. Vous devez veiller à ne pas les écraser.
N’ajoutez aucun chemin d’importation superflu. Le moteur QML risquerait alors de trouver vos modules au mauvais endroit. Cela peut entraîner des problèmes qui ne peuvent être reproduits que dans des environnements spécifiques.
Intégration de plugins QML personnalisés
Si vous intégrez une image provider dans le module QML, vous devez implémenter la méthode QQmlEngineExtensionPlugin::initializeEngine(). Cela implique donc de devoir écrire votre propre plugin. Pour prendre en charge ce cas d’utilisation, vous pouvez utiliser NO_GENERATE_PLUGIN_SOURCE.
Prenons l’exemple d’un module fournissant sa propre source de plugin :
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
)Vous pouvez déclarer un fournisseur d’images dans myimageprovider.h, comme ceci :
class MyImageProvider : public QQuickImageProvider
{
[...]
};Dans plugin.cpp, vous pouvez ensuite définir l’ 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);
}
};Cela rendra le fournisseur d’images disponible. Le plugin et la bibliothèque sous-jacente se trouvent tous deux dans la même cible CMake, imageproviderplugin. Cette approche permet d’éviter que l’éditeur de liens ne supprime certaines parties du module dans divers scénarios.
Comme le plugin crée un fournisseur d’images, il ne dispose plus d’une fonction ` initializeEngine ` triviale. Par conséquent, le plugin n’est plus facultatif.
Voir également Modifications apportées à ` Qt Qml`, Modernisation des modules QML et Portage des modules QML vers CMake.
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.