Sur cette page

Les singletons en QML

En QML, un singleton est un objet qui n'est créé qu'une seule 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 les singletons.

Comment créer des singletons en QML ?

Il existe deux méthodes distinctes pour créer des singletons en QML. Vous pouvez soit définir le singleton dans un fichier QML, soit l'enregistrer depuis le C++.

Définition de singletons en QML

Pour définir un singleton en QML, vous devez d’abord ajouter

pragma Singleton

en haut de votre fichier. Il reste une étape supplémentaire : vous devrez ajouter une entrée au fichier qmldir du module QML.

Utilisation de qt_add_qml_module (CMake)

Lorsque vous utilisez CMake, le fichier `qmldir` est automatiquement créé par `qt_add_qml_module`. Pour indiquer que le fichier QML doit être transformé en singleton, vous devez lui attribuer la propriété de fichier ` QT_QML_SINGLETON_TYPE ` :

set_source_files_properties(MySingleton.qml
    PROPERTIES QT_QML_SINGLETON_TYPE TRUE)

Vous pouvez passer plusieurs fichiers à la fois à la fonction ` 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}
)

Remarque : la fonction set_source_files_properties doit être appelée 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`. Dans ce fichier, vous devrez marquer vos singletons en conséquence :

module MyModule
singleton MySingleton 1.0 MySingleton.qml
singleton MyOtherSingleton 1.0 MyOtherSingleton.qml

Voir également Déclaration des types d’objets pour plus de détails.

Définition des singletons en C++

Il existe plusieurs façons d’exposer des singletons à QML depuis C++. La principale différence réside dans le fait 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 manière la plus simple de définir un singleton consiste à disposer d’une classe constructible par défaut, dérivée de ` QObject `, et à la marquer à l’aide des 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 appartient le fichier. Si vous souhaitez l’exposer sous un autre nom, vous pouvez utiliser ` QML_NAMED_ELEMENT ` à la place.

Si la classe ne peut pas être rendue constructible par défaut, ou si vous avez besoin d’accéder à l’ QQmlEngine dans laquelle le singleton est instancié, il est possible d’utiliser à la place une fonction statique `create`. 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 à la fois un paramètre de type ` QJSEngine ` et un paramètre de type ` QQmlEngine `. Cela s’explique par des raisons historiques. Ces deux paramètres pointent vers le même objet, qui est en réalité de type ` QQmlEngine`.

Exposer un objet instancié en C++ en tant que singleton

Il peut être utile de contrôler l’instanciation d’un singleton depuis le C++, plutôt que de laisser l’ QQmlEngine instancier l’instance. De tels singletons n’ont pas besoin d’être instanciables par le moteur ; vous pouvez donc vous passer d’un constructeur par défaut ou d’une fonction statique create. Si vous le faites, vous devez inclure la macro QML_UNCREATABLE dans la déclaration du singleton :

class MyNonDefaultConstructibleSingleton : public QObject
{
    Q_OBJECT
    QML_SINGLETON
    QML_NAMED_ELEMENT(MySingleton)
    QML_UNCREATABLE("Provided by C++")
public:
    MyNonDefaultConstructibleSingleton(BackendObject* backend, QObject *parent = nullptr)
        : QObject(parent)
        , m_backend(backend)
    {}

private:
    class BackendObject* backend;
};

Ensuite, nous configurons l’instance à utiliser dans le moteur, avant de le démarrer :

MyNonDefaultConstructibleSingleton singleton(backend);
QQmlApplicationEngine engine;
engine.setExternalSingletonInstance("MyModule", "MySingleton", &singleton);
engine.loadFromModule("MyModule", "Main");

Exposer un objet existant en tant que singleton

Il arrive parfois que vous disposiez d’un objet existant qui a pu être créé via une API tierce. Souvent, la bonne solution dans ce cas consiste à créer un singleton qui expose ces objets en tant que propriétés (voir Regroupement de données associées). Mais si ce n’est pas le cas, par exemple parce qu’il n’y a qu’un seul objet à exposer, utilisez l’approche suivante pour exposer une instance de type MySingleton au moteur. Nous exposons d’abord le singleton en tant qu’ foreign type:

struct SingletonForeign
{
    Q_GADGET
    QML_FOREIGN(MySingleton)
    QML_SINGLETON
    QML_NAMED_ELEMENT(MySingleton)
    QML_UNCREATABLE("Provided from C++")
};

Ensuite, il suffit d’instancier le `MySingleton` et de le définir dans le moteur :

MySingleton instance = getSingletonInstance();
QQmlApplicationEngine engine;
engine.setExternalSingletonInstance("MyModule", "MySingleton", &instance);
engine.loadFromModule("MyModule", "Main");

Remarque : il peut être très tentant d’utiliser simplement ` qmlRegisterSingletonInstance ` dans ce cas. Cependant, méfiez-vous des pièges liés à l’enregistrement impératif des types, énumérés dans la section suivante.

Enregistrement impératif des types

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 le léger inconvénient 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 principal problème de cette approche était qu’elle n’était pas adaptée aux outils : il n’était pas possible d’extraire de manière statique toutes les informations nécessaires sur les types d’un module lors 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: c’est un moyen d’exposer un singleton de type non-QObject e en tant que propriété var via the QJSValue based qmlRegisterSingletonType overload . Privilégiez l’alternative suivante : exposez cette valeur en tant que propriété d’un singleton basé sur (QObject), afin que les informations de type soient disponibles.

Accès aux singletons

Les singletons sont accessibles aussi bien depuis QML que depuis C++. En QML, vous devez importer le module qui les contient. Vous pouvez ensuite accéder au singleton via son nom. La lecture et la modification de ses propriétés dans des contextes JavaScript s’effectuent de la même manière que pour les objets normaux :

import QtQuick
import MyModule

Item {
    x: MySingleton.posX
    Component.onCompleted: MySingleton.ready = true;
}

Il n’est pas possible de configurer des liaisons sur les propriétés d’un singleton ; toutefois, si cela s’avère 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’êtreprudent lors de la mise en place d’une liaison sur une propriété de singleton : si cette opération est effectuée par plusieurs fichiers, les résultats ne sont pas définis.

Recommandations concernant l’utilisation (ou non) des singletons

Les singletons vous permettent d’exposer au moteur des données auxquelles il faut accéder à plusieurs endroits. Il peut s’agir de paramètres partagés globalement, comme l’espacement entre les éléments, ou de modèles de données qui doivent être affichés à plusieurs endroits. Par rapport aux propriétés de contexte qui peuvent résoudre un cas d’utilisation similaire, ils présentent l’avantage d’être typés, d’être pris en charge par des outils tels que QML Language Server, et d’être généralement plus rapides lors de l’exécution.

Il est recommandé de ne pas enregistrer trop de singletons dans un module : une fois créés, les singletons restent actifs jusqu’à la destruction du moteur lui-même et présentent les inconvénients liés à l’état partagé, car ils font partie de l’état global. Envisagez donc d’utiliser les techniques suivantes pour réduire le nombre de singletons dans votre application :

Ajouter un singleton pour chaque objet que vous souhaitez exposer génère beaucoup de code répétitif. 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 seul singleton. Supposons par exemple que vous souhaitiez créer une liseuse électronique où vous devez exposer trois objets ` 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 seul 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 section précédente, nous avons vu un exemple d’exposition de trois modèles en tant que membres d’un singleton. Cela peut s’avérer utile soit lorsque les modèles doivent être utilisés à plusieurs endroits, soit 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 définir comme 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 transmettre un état à QML, ils constituent un gaspillage lorsque cet état n’est nécessaire que pour la configuration initiale de l’application. Dans ce cas, il est souvent possible d’utiliser des « QQmlApplicationEngine::setInitialProperties ». Vous pourriez par exemple vouloir définir Window::visibility en mode plein écran si un indicateur de ligne de commande correspondant a été activé :

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.