Singletons en QML
En QML, un singleton est un objet qui est créé au maximum une fois par engine. Dans ce guide, nous expliquerons comment créer des singletons et comment les utiliser. Nous présenterons également quelques bonnes pratiques pour travailler avec des singletons.
Comment créer des singletons en QML ?
Il existe deux manières distinctes de créer des singletons en QML. Vous pouvez soit définir le singleton dans un fichier QML, soit l'enregistrer à partir de C++.
Définition des singletons en QML
Pour définir un singleton en QML, vous devez d'abord ajouter
pragma Singleton
au début de votre fichier. Il y a encore une étape à franchir : Vous devez ajouter une entrée au fichier qmldir du module QML.
Utilisation de qt_add_qml_module (CMake)
Lorsque l'on utilise CMake, le qmldir est automatiquement créé par qt_add_qml_module. Pour indiquer que le fichier QML doit être transformé en un singleton, vous devez définir la propriété QT_QML_SINGLETON_TYPE file :
set_source_files_properties(MySingleton.qml
PROPERTIES QT_QML_SINGLETON_TYPE TRUE)Vous pouvez passer plusieurs fichiers à la fois à set_source_files_properties:
set(plain_qml_files
MyItem1.qml
MyItem2.qml
FancyButton.qml
)
set(qml_singletons
MySingleton.qml
MyOtherSingleton.qml
)
set_source_files_properties(${qml_singletons}
PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
qt_add_qml_module(myapp
URI MyModule
QML_FILES ${plain_qml_files} ${qml_singletons}
)Note : set_source_files_properties doit être appelé avant qt_add_qml_module
Sans qt_add_qml_module
Si vous n'utilisez pas qt_add_qml_module, vous devrez créer manuellement un fichier qmldir. Là, vous devrez marquer vos singletons en conséquence :
module MyModule singleton MySingleton 1.0 MySingleton.qml singleton MyOtherSingleton 1.0 MyOtherSingleton.qml
Voir aussi Déclaration de type d'objet pour plus de détails.
Définir les singletons en C
Il existe plusieurs façons d'exposer les singletons à QML à partir de C++. La principale différence dépend de la question de savoir si une nouvelle instance d'une classe doit être créée lorsque le moteur QML en a besoin, ou si un objet existant doit être exposé à un programme QML.
Enregistrement d'une classe pour fournir des singletons
La façon la plus simple de définir un singleton est d'avoir une classe constructible par défaut, qui dérive de QObject et de la marquer avec les macros QML_SINGLETON et QML_ELEMENT.
class MySingleton : public QObject { Q_OBJECT QML_SINGLETON QML_ELEMENT public: MySingleton(QObject *parent = nullptr) : QObject(parent) { // ... } };
Cela enregistrera la classe MySingleton sous le nom MySingleton dans le module QML auquel le fichier appartient. Si vous souhaitez l'exposer sous un autre nom, vous pouvez utiliser QML_NAMED_ELEMENT à la place.
Si la classe ne peut pas être construite par défaut, ou si vous avez besoin d'accéder au site QQmlEngine dans lequel le singleton est instancié, il est possible d'utiliser une fonction de création statique à la place. Elle doit avoir la signature MySingleton *create(QQmlEngine *, QJSEngine *), où MySingleton est le type de la classe qui est enregistrée.
class MyNonDefaultConstructibleSingleton : public QObject { Q_OBJECT QML_SINGLETON QML_NAMED_ELEMENT(MySingleton) public: MyNonDefaultConstructibleSingleton(QJSValue id, QObject *parent = nullptr) : QObject(parent) , m_symbol(std::move(id)) {} static MyNonDefaultConstructibleSingleton *create(QQmlEngine *qmlEngine, QJSEngine *) { return new MyNonDefaultConstructibleSingleton(qmlEngine->newSymbol(u"MySingleton"_s)); } private: QJSValue m_symbol; };
Remarque : la fonction create prend un paramètre QJSEngine et un paramètre QQmlEngine. C'est pour des raisons historiques. Ils pointent tous deux vers le même objet, qui est en fait un QQmlEngine.
Exposer un objet existant en tant que singleton
Parfois, vous disposez d'un objet existant qui peut avoir été créé via une API tierce. Souvent, le bon choix dans ce cas est d'avoir un singleton, qui expose ces objets en tant que propriétés (voir Regrouper des données connexes). Mais si ce n'est pas le cas, par exemple parce qu'il n'y a qu'un seul objet qui doit être exposé, utilisez l'approche suivante pour exposer une instance de type MySingleton au moteur. Nous exposons d'abord le Singleton en tant que foreign type:
struct SingletonForeign { Q_GADGET QML_FOREIGN(MySingleton) QML_SINGLETON QML_NAMED_ELEMENT(MySingleton) public: inline static MySingleton *s_singletonInstance = nullptr; static MySingleton *create(QQmlEngine *, QJSEngine *engine) { // The instance has to exist before it is used. We cannot replace it. Q_ASSERT(s_singletonInstance); // The engine has to have the same thread affinity as the singleton. Q_ASSERT(engine->thread() == s_singletonInstance->thread()); // There can only be one engine accessing the singleton. if (s_engine) Q_ASSERT(engine == s_engine); else s_engine = engine; // Explicitly specify C++ ownership so that the engine doesn't delete // the instance. QJSEngine::setObjectOwnership(s_singletonInstance, QJSEngine::CppOwnership); return s_singletonInstance; } private: inline static QJSEngine *s_engine = nullptr; };
Ensuite, nous définissons SingletonForeign::s_singletonInstance avant de démarrer le premier moteur.
SingletonForeign::s_singletonInstance = getSingletonInstance(); QQmlApplicationEngine engine; engine.loadFromModule("MyModule", "Main");
Remarque : il peut être très tentant d'utiliser simplement qmlRegisterSingletonInstance dans ce cas. Cependant, il faut se méfier des pièges de l'enregistrement impératif des types énumérés dans la section suivante.
Enregistrement de type impératif
Avant Qt 5.15, tous les types, y compris les singletons, étaient enregistrés via l'API qmlRegisterType. Les singletons en particulier étaient enregistrés via qmlRegisterSingletonType ou qmlRegisterSingletonInstance. Outre l'inconvénient mineur de devoir répéter le nom du module pour chaque type et le découplage forcé entre la déclaration de la classe et son enregistrement, le problème majeur de cette approche était qu'elle n'était pas adaptée à l'outillage : il n'était pas possible d'extraire statiquement toutes les informations nécessaires sur les types d'un module au moment de la compilation. L'enregistrement déclaratif a résolu ce problème.
Remarque : il reste un cas d'utilisation pour l'API impérative qmlRegisterType: Il s'agit d'un moyen d'exposer un singleton de type nonQObject en tant que propriété var via the QJSValue based qmlRegisterSingletonType overload . Préférez l'alternative : Exposer cette valeur en tant que propriété d'un singleton basé sur (QObject), de sorte que l'information sur le type soit disponible.
Accès aux singletons
Les singletons sont accessibles aussi bien en QML qu'en C++. En QML, vous devez importer le module contenant le singleton. Ensuite, vous pouvez accéder au singleton via son nom. La lecture et l'écriture de ses propriétés dans des contextes JavaScript s'effectuent de la même manière qu'avec des objets normaux :
import QtQuick import MyModule Item { x: MySingleton.posX Component.onCompleted: MySingleton.ready = true; }
Il n'est pas possible d'établir des liaisons sur les propriétés d'un singleton ; toutefois, si cela est nécessaire, un élément Binding peut être utilisé pour obtenir le même résultat :
import QtQuick import MyModule Item { id: root Binding { target: MySingleton property: "posX" value: root.x } }
Remarque : il convient d'être prudent lors de l'installation d'une liaison sur une propriété singleton : Si cela est fait par plus d'un fichier, les résultats ne sont pas définis.
Lignes directrices pour (ne pas) utiliser les singletons
Les singletons vous permettent d'exposer au moteur des données qui doivent être accessibles à plusieurs endroits. Il peut s'agir de paramètres globalement partagés, comme l'espacement entre les éléments, ou de modèles de données qui doivent être affichés à plusieurs endroits. Comparées aux propriétés de contexte qui peuvent résoudre un cas d'utilisation similaire, elles ont l'avantage d'être typées, d'être supportées par des outils tels que la fonction QML Language Serveret elles sont généralement plus rapides à l'exécution.
Il est recommandé de ne pas enregistrer trop de singletons dans un module : Les singletons, une fois créés, restent en vie jusqu'à ce que le moteur lui-même soit détruit et présentent les inconvénients d'un état partagé puisqu'ils font partie de l'état global. Envisagez donc d'utiliser les techniques suivantes pour réduire le nombre de singletons dans votre application :
Regrouper les données connexes
L'ajout d'un singleton pour chaque objet que vous souhaitez exposer ajoute une certaine quantité de données. La plupart du temps, il est plus judicieux de regrouper les données que vous souhaitez exposer en tant que propriétés d'un singleton unique. Supposons, par exemple, que vous souhaitiez créer un lecteur de livres électroniques pour lequel vous devez exposer trois abstract item models, un pour les livres locaux et deux pour les sources distantes. Au lieu de répéter trois fois le processus d'exposition des objets existants, vous pouvez créer un singleton et le configurer avant de lancer l'application principale :
class GlobalState : QObject { Q_OBJECT QML_ELEMENT QML_SINGLETON Q_PROPERTY(QAbstractItemModel* localBooks MEMBER localBooks) Q_PROPERTY(QAbstractItemModel* digitalStoreFront MEMBER digitalStoreFront) Q_PROPERTY(QAbstractItemModel* publicLibrary MEMBER publicLibrary) public: QAbstractItemModel* localBooks; QAbstractItemModel* digitalStoreFront; QAbstractItemModel* publicLibrary }; int main() { QQmlApplicationEngine engine; auto globalState = engine.singletonInstance<GlobalState *>("MyModule", "GlobalState"); globalState->localBooks = getLocalBooks(); globalState->digitalStoreFront = setupLoalStoreFront(); globalState->publicLibrary = accessPublicLibrary(); engine.loadFromModule("MyModule", "Main"); }
Utiliser des instances d'objets
Dans la dernière section, nous avons pris l'exemple de l'exposition de trois modèles en tant que membres d'un singleton. Cela peut être utile lorsque les modèles doivent être utilisés à plusieurs endroits, ou lorsqu'ils sont fournis par une API externe sur laquelle nous n'avons aucun contrôle. Cependant, si nous n'avons besoin des modèles qu'à un seul endroit, il peut être plus judicieux de les avoir sous la forme d'un type instanciable. Pour revenir à l'exemple précédent, nous pouvons ajouter une classe RemoteBookModel instanciable, puis l'instancier dans le fichier QML du navigateur de livres :
// remotebookmodel.h class RemoteBookModel : public QAbstractItemModel { Q_OBJECT QML_ELEMENT Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged) // ... };
// bookbrowser.qml Row { ListView { model: RemoteBookModel { url: "www.public-lib.example"} } ListView { model: RemoteBookModel { url: "www.store-front.example"} } }
Transmission de l'état initial
Bien que les singletons puissent être utilisés pour passer de l'état à QML, ils sont inutiles lorsque l'état n'est nécessaire que pour la configuration initiale de l'application. Dans ce cas, il est souvent possible d'utiliser QQmlApplicationEngine::setInitialProperties. Vous pourriez par exemple vouloir régler Window::visibility sur fullscreen si un drapeau de ligne de commande correspondant a été défini :
QQmlApplicationEngine engine; if (parser.isSet(fullScreenOption)) { // assumes root item is ApplicationWindow engine.setInitialProperties( { "visibility", QVariant::fromValue(QWindow::FullScreen)} ); } engine.loadFromModule("MyModule, "Main");
© 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.