Singletons in QML

In QML ist ein Singleton ein Objekt, das höchstens einmal pro engine erstellt wird. In diesem Handbuch wird erklärt, wie Singletons erstellt werden und wie man sie verwendet. Außerdem werden wir einige bewährte Verfahren für die Arbeit mit Singletons vorstellen.

Wie können Singletons in QML erstellt werden?

Es gibt zwei verschiedene Möglichkeiten, Singletons in QML zu erstellen. Sie können das Singleton entweder in einer QML-Datei definieren oder es in C++ registrieren.

Definieren von Singletons in QML

Um ein Singleton in QML zu definieren, müssen Sie zunächst

pragma Singleton

an den Anfang Ihrer Datei setzen. Es gibt noch einen weiteren Schritt: Sie müssen einen Eintrag in der qmldir-Datei des QML-Moduls hinzufügen.

Verwendung von qt_add_qml_module (CMake)

Bei der Verwendung von CMake wird das qmldir automatisch von qt_add_qml_module erstellt. Um anzugeben, dass die QML-Datei in eine Singleton-Datei umgewandelt werden soll, müssen Sie die Eigenschaft QT_QML_SINGLETON_TYPE file dafür setzen:

set_source_files_properties(MySingleton.qml
    PROPERTIES QT_QML_SINGLETON_TYPE TRUE)

Sie können mehrere Dateien auf einmal an set_source_files_properties übergeben:

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}
)

Hinweis: set_source_files_properties muss vorher aufgerufen werden qt_add_qml_module

ohne qt_add_qml_module

Wenn Sie nicht qt_add_qml_module verwenden, müssen Sie manuell eine qmldir-Datei erstellen. Dort müssen Sie Ihre Singletons entsprechend markieren:

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

Siehe auch Objekttypdeklaration für weitere Details.

Definieren von Singletons in C++

Es gibt mehrere Möglichkeiten, Singletons von C++ aus in QML darzustellen. Der Hauptunterschied hängt davon ab, ob eine neue Instanz einer Klasse erstellt werden soll, wenn sie von der QML-Engine benötigt wird, oder ob ein vorhandenes Objekt einem QML-Programm zugänglich gemacht werden soll.

Registrierung einer Klasse zur Bereitstellung von Singletons

Der einfachste Weg, ein Singleton zu definieren, besteht darin, eine standardmäßig konstruierbare Klasse zu haben, die von QObject abgeleitet ist, und sie mit den Makros QML_SINGLETON und QML_ELEMENT zu markieren.

class MySingleton : public QObject
{
    Q_OBJECT
    QML_SINGLETON
    QML_ELEMENT
public:
    MySingleton(QObject *parent = nullptr) : QObject(parent) {
        // ...
    }
};

Dadurch wird die Klasse MySingleton unter dem Namen MySingleton in dem QML-Modul registriert, zu dem die Datei gehört. Wenn Sie die Klasse unter einem anderen Namen ausstellen wollen, können Sie stattdessen QML_NAMED_ELEMENT verwenden.

Wenn die Klasse nicht standardmäßig konstruierbar gemacht werden kann oder wenn Sie Zugriff auf die QQmlEngine benötigen, in der das Singleton instanziiert wird, ist es möglich, stattdessen eine statische Erstellungsfunktion zu verwenden. Sie muss die Signatur MySingleton *create(QQmlEngine *, QJSEngine *) haben, wobei MySingleton der Typ der Klasse ist, die registriert wird.

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;
};

Hinweis: Die create-Funktion nimmt sowohl einen QJSEngine als auch einen QQmlEngine Parameter an. Dies ist aus historischen Gründen so. Sie verweisen beide auf dasselbe Objekt, das in Wirklichkeit ein QQmlEngine ist.

Ein vorhandenes Objekt als Singleton freigeben

Manchmal haben Sie ein vorhandenes Objekt, das möglicherweise über eine API eines Drittanbieters erstellt wurde. In diesem Fall ist es oft die richtige Wahl, ein Singleton zu haben, das diese Objekte als seine Eigenschaften offenlegt (siehe Gruppierung zusammengehöriger Daten). Wenn dies jedoch nicht der Fall ist, zum Beispiel weil nur ein einziges Objekt offengelegt werden muss, verwenden Sie den folgenden Ansatz, um eine Instanz des Typs MySingleton für die Engine offenzulegen. Zuerst stellen wir das Singleton als foreign type dar:

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;
};

Dann setzen wir SingletonForeign::s_singletonInstance bevor wir die erste Engine starten.

SingletonForeign::s_singletonInstance = getSingletonInstance();
QQmlApplicationEngine engine;
engine.loadFromModule("MyModule", "Main");

Hinweis: Es kann sehr verlockend sein, in diesem Fall einfach qmlRegisterSingletonInstance zu verwenden. Seien Sie jedoch vorsichtig mit den Fallstricken der imperativen Typregistrierung, die im nächsten Abschnitt aufgeführt sind.

Imperative Typregistrierung

Vor Qt 5.15 wurden alle Typen, einschließlich Singletons, über die qmlRegisterType API registriert. Singletons wurden entweder über qmlRegisterSingletonType oder qmlRegisterSingletonInstance registriert. Neben dem kleinen Ärgernis, den Modulnamen für jeden Typ wiederholen zu müssen, und der erzwungenen Entkopplung von Klassendeklaration und Registrierung war das Hauptproblem dieses Ansatzes, dass er nicht werkzeugfreundlich ist: Es war nicht statisch möglich, alle notwendigen Informationen über die Typen eines Moduls zur Kompilierzeit zu extrahieren. Die deklarative Registrierung löste dieses Problem.

Hinweis: Es gibt noch einen Anwendungsfall für die imperative qmlRegisterType API: Es handelt sich um eine Möglichkeit, ein Singleton eines Nicht-QObject -Typs als var -Eigenschaft über the QJSValue based qmlRegisterSingletonType overload . Ziehen Sie die Alternative vor: Diesen Wert als Eigenschaft eines (QObject) basierten Singletons ausgeben, so dass die Typinformation verfügbar ist.

Zugriff auf Singletons

Auf Singletons kann sowohl von QML als auch von C++ aus zugegriffen werden. In QML müssen Sie das enthaltende Modul importieren. Danach kann man auf das Singleton über seinen Namen zugreifen. Das Lesen seiner Eigenschaften und das Schreiben in JavaScript-Kontexten erfolgt auf die gleiche Weise wie bei normalen Objekten:

import QtQuick
import MyModule

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

Das Einrichten von Bindungen an die Eigenschaften eines Singletons ist nicht möglich; falls dies jedoch erforderlich ist, kann ein Binding -Element verwendet werden, um das gleiche Ergebnis zu erzielen:

import QtQuick
import MyModule

Item {
    id: root
    Binding {
        target: MySingleton
        property: "posX"
        value: root.x
    }
}

Hinweis: Beim Einrichten einer Bindung an eine Singleton-Eigenschaft ist Vorsicht geboten: Wenn dies durch mehr als eine Datei geschieht, sind die Ergebnisse nicht definiert.

Richtlinien für die (Nicht-)Verwendung von Singletons

Mit Singletons können Sie Daten, auf die an mehreren Stellen zugegriffen werden muss, der Engine zur Verfügung stellen. Das können global geteilte Einstellungen sein, wie der Abstand zwischen Elementen, oder Datenmodelle, die an mehreren Stellen angezeigt werden müssen. Im Vergleich zu Kontexteigenschaften, die einen ähnlichen Anwendungsfall lösen können, haben sie den Vorteil, dass sie typisiert sind, von Werkzeugen wie dem QML Language Serverunterstützt werden, und sie sind im Allgemeinen auch schneller zur Laufzeit.

Es wird empfohlen, nicht zu viele Singletons in einem Modul zu registrieren: Einmal erstellte Singletons bleiben am Leben, bis die Engine selbst zerstört wird, und sie haben den Nachteil eines gemeinsam genutzten Zustands, da sie Teil des globalen Zustands sind. Daher sollten Sie die folgenden Techniken anwenden, um die Anzahl der Singletons in Ihrer Anwendung zu reduzieren:

Das Hinzufügen eines Singletons für jedes Objekt, das Sie offenlegen wollen, führt zu einem erheblichen Mehraufwand. In den meisten Fällen ist es sinnvoller, die Daten, die Sie offenlegen wollen, als Eigenschaften eines einzigen Singletons zu gruppieren. Nehmen wir zum Beispiel an, dass Sie einen eBook-Reader erstellen wollen, für den Sie drei abstract item models zur Verfügung stellen müssen, eines für lokale Bücher und zwei für entfernte Quellen. Anstatt den Prozess zur Offenlegung vorhandener Objekte dreimal zu wiederholen, können Sie stattdessen ein Singleton erstellen und es vor dem Start der Hauptanwendung einrichten:

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");
}

Objektinstanzen verwenden

Im letzten Abschnitt hatten wir das Beispiel, drei Modelle als Mitglieder eines Singletons darzustellen. Das kann nützlich sein, wenn die Modelle entweder an mehreren Stellen verwendet werden müssen oder wenn sie von einer externen API bereitgestellt werden, über die wir keine Kontrolle haben. Wenn wir die Modelle jedoch nur an einer einzigen Stelle benötigen, kann es sinnvoller sein, sie als instanzierbaren Typ zu haben. Um auf das vorherige Beispiel zurückzukommen, können wir eine instanzierbare RemoteBookModel-Klasse hinzufügen und sie dann innerhalb der QML-Datei des Buchbrowsers instanziieren:

// 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"}
    }
}

Übergabe des Anfangszustands

Singletons können zwar zur Übergabe von Zuständen an QML verwendet werden, sie sind jedoch überflüssig, wenn der Zustand nur für die anfängliche Einrichtung der Anwendung benötigt wird. In diesem Fall ist es oft möglich, QQmlApplicationEngine::setInitialProperties zu verwenden. Sie könnten zum Beispiel Window::visibility auf Vollbild setzen, wenn ein entsprechendes Kommandozeilenflag gesetzt wurde:

QQmlApplicationEngine engine;
if (parser.isSet(fullScreenOption)) {
    // assumes root item is ApplicationWindow
    engine.setInitialProperties(
        { "visibility", QVariant::fromValue(QWindow::FullScreen)}
    );
}
engine.loadFromModule("MyModule, "Main");

© 2025 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.