Interaktion mit QML-Objekten von C++ aus

Alle QML-Objekttypen sind QObject-abgeleitete Typen, unabhängig davon, ob sie intern von der Engine implementiert oder von Drittanbietern definiert wurden. Das bedeutet, dass die QML-Engine das Qt Meta Object System verwenden kann, um jeden QML-Objekttyp dynamisch zu instanziieren und die erstellten Objekte zu untersuchen.

Dies ist nützlich für die Erstellung von QML-Objekten aus C++-Code, sei es, um ein QML-Objekt anzuzeigen, das visuell gerendert werden kann, oder um nicht visuelle QML-Objektdaten in eine C++-Anwendung zu integrieren. Sobald ein QML-Objekt erstellt ist, kann es von C++ aus inspiziert werden, um Eigenschaften zu lesen und zu schreiben, Methoden aufzurufen und Signalbenachrichtigungen zu empfangen.

Weitere Informationen über C++ und die verschiedenen QML-Integrationsmethoden finden Sie auf der Übersichtsseite zu C++ und QML-Integration.

Laden von QML-Objekten aus C++

Ein QML-Dokument kann mit QQmlComponent oder QQuickView geladen werden. QQmlComponent lädt ein QML-Dokument als C++-Objekt, das dann von C++-Code aus geändert werden kann. QQuickView tut dies ebenfalls, aber da QQuickView eine von QWindow abgeleitete Klasse ist, wird das geladene Objekt auch in eine visuelle Anzeige gerendert; QQuickView wird im Allgemeinen verwendet, um ein anzeigbares QML-Objekt in die Benutzeroberfläche einer Anwendung zu integrieren.

Nehmen wir zum Beispiel an, es gibt eine MyItem.qml Datei, die wie folgt aussieht:

import QtQuick

Item {
    width: 100; height: 100
}

Dieses QML-Dokument kann mit QQmlComponent oder QQuickView mit dem folgenden C++-Code geladen werden. Die Verwendung von QQmlComponent erfordert den Aufruf von QQmlComponent::create(), um eine neue Instanz der Komponente zu erzeugen, während QQuickView automatisch eine Instanz der Komponente erzeugt, die über QQuickView::rootObject() zugänglich ist:

// Using QQmlComponent
QQmlEngine engine;
QQmlComponent component(&engine,
        QUrl::fromLocalFile("MyItem.qml"));
QObject *object = component.create();
...
delete object;
// Using QQuickView
QQuickView view;
view.setSource(QUrl::fromLocalFile("MyItem.qml"));
view.show();
QObject *object = view.rootObject();

Diese object ist die Instanz der MyItem.qml Komponente, die erstellt wurde. Sie können nun die Eigenschaften des Elements mit QObject::setProperty() oder QQmlProperty::write() ändern:

object->setProperty("width", 500);
QQmlProperty(object, "width").write(500);

Der Unterschied zwischen QObject::setProperty() und QQmlProperty::write() besteht darin, dass bei letzterem zusätzlich zum Setzen des Eigenschaftswerts auch die Bindung aufgehoben wird. Nehmen wir zum Beispiel an, die obige Zuweisung width sei eine Bindung an height gewesen:

width: height

Wenn sich height von Item nach dem Aufruf von object->setProperty("width", 500) ändert, würde width erneut aktualisiert werden, da die Bindung aktiv bleibt. Ändert sich jedoch height nach dem Aufruf von QQmlProperty(object, "width").write(500), wird width nicht geändert, da die Bindung nicht mehr besteht.

Alternativ können Sie das Objekt auf seinen eigentlichen Typ casten und Methoden mit Kompilierzeitsicherheit aufrufen. In diesem Fall ist das Basisobjekt von MyItem.qml ein Item, das durch die Klasse QQuickItem definiert ist:

QQuickItem *item = qobject_cast<QQuickItem*>(object);
item->setWidth(500);

Sie können sich auch mit Signalen verbinden oder Methoden aufrufen, die in der Komponente mit QMetaObject::invokeMethod() und QObject::connect() definiert sind. Weitere Einzelheiten finden Sie unter Aufrufen von QML-Methoden und Verbinden mit QML-Signalen.

Zugriff auf QML-Objekte über wohldefinierte C++-Schnittstellen

Der beste Weg, um mit QML von C++ aus zu interagieren, ist die Definition einer Schnittstelle in C++ und der Zugriff auf diese Schnittstelle in QML selbst. Bei anderen Methoden kann das Refactoring Ihres QML-Codes leicht dazu führen, dass Ihre QML/C++-Interaktion abbricht. Es ist auch hilfreich, die Interaktion von QML und C++-Code zu durchdenken, da sie über QML gesteuert wird und somit sowohl von Benutzern als auch von Werkzeugen wie qmllint leichter durchschaut werden kann. Der Zugriff auf QML von C++ aus führt zu QML-Code, der nicht verstanden werden kann, ohne manuell zu überprüfen, dass kein externer C++-Code eine bestimmte QML-Komponente modifiziert, und selbst dann kann sich das Ausmaß des Zugriffs im Laufe der Zeit ändern, was die fortgesetzte Verwendung dieser Strategie zu einem Wartungsaufwand macht.

Um die Interaktion von QML steuern zu lassen, müssen Sie zunächst eine C++-Schnittstelle definieren:

class CppInterface : public QObject
{
    Q_OBJECT
    QML_ELEMENT
    // ...
};

Mit einem QML-gesteuerten Ansatz kann mit dieser Schnittstelle auf zwei Arten interagiert werden:

Singletons

Eine Möglichkeit besteht darin, die Schnittstelle als Singleton zu registrieren, indem man das Makro QML_SINGLETON zur Schnittstelle hinzufügt, wodurch sie für alle Komponenten zugänglich wird. Anschließend wird die Schnittstelle über eine einfache Importanweisung verfügbar:

import my.company.module

Item {
    Component.onCompleted: {
        CppInterface.foo();
    }
}

Verwenden Sie diesen Ansatz, wenn Sie Ihre Schnittstelle an mehr Stellen als der Stammkomponente benötigen, da die einfache Weitergabe eines Objekts die explizite Weitergabe an andere Komponenten über eine Eigenschaft oder die langsame und nicht empfohlene Methode des unqualifizierten Zugriffs erfordern würde.

Anfängliche Eigenschaften

Eine weitere Möglichkeit besteht darin, die Schnittstelle über QML_UNCREATABLE als unerstellbar zu markieren und sie der QML-Stammkomponente mit QQmlComponent::createWithInitialProperties() und einer erforderlichen Eigenschaft auf der QML-Seite zu übergeben.

Ihre Stammkomponente könnte etwa so aussehen:

import QtQuick

Item {
    required property CppInterface interface
    Component.onCompleted: {
        interface.foo();
    }
}

Die Kennzeichnung der Eigenschaft als erforderlich schützt die Komponente davor, dass sie erstellt wird, ohne dass die Schnittstelleneigenschaft gesetzt ist.

Sie können Ihre Komponente dann auf die gleiche Weise initialisieren, wie in Laden von QML-Objekten aus C++ beschrieben, außer mit createWithInitialProperties():

component.createWithInitialProperties(QVariantMap{{u"interface"_s, QVariant::fromValue<CppInterface *>(new CppInterface)}});

Diese Methode ist zu bevorzugen, wenn Sie wissen, dass Ihre Schnittstelle nur für die Stammkomponente verfügbar sein muss. Sie ermöglicht auch eine einfachere Verbindung zu Signalen und Slots der Schnittstelle auf der C++-Seite.

Wenn keine der beiden Methoden Ihren Anforderungen entspricht, sollten Sie stattdessen die Verwendung von C++-Modellen in Betracht ziehen.

Zugriff auf geladene QML-Objekte über den Objektnamen

QML-Komponenten sind im Wesentlichen Objektbäume mit Kindern, die Geschwister und eigene Kinder haben. Untergeordnete Objekte von QML-Komponenten können über die Eigenschaft QObject::objectName mit QObject::findChild() gefunden werden. Zum Beispiel, wenn das Wurzelelement in MyItem.qml ein untergeordnetes Element Rectangle hat:

import QtQuick

Item {
    width: 100; height: 100

    Rectangle {
        anchors.fill: parent
        objectName: "rect"
    }
}

Das Kind könnte wie folgt gefunden werden:

QObject *rect = object->findChild<QObject*>("rect");
if (rect)
    rect->setProperty("color", "red");

Beachten Sie, dass ein Objekt mehrere Kinder mit demselben objectName haben kann. Zum Beispiel erstellt ListView mehrere Instanzen seines Delegaten. Wenn also sein Delegat mit einem bestimmten Objektnamen deklariert ist, hat ListView mehrere Kinder mit demselben objectName. In diesem Fall kann QObject::findChildren() verwendet werden, um alle Kinder mit einem passenden objectName zu finden.

Warnung: Obwohl es möglich ist, von C++ aus auf QML-Objekte zuzugreifen und sie zu manipulieren, ist dies nicht der empfohlene Ansatz, außer für Test- und Prototyping-Zwecke. Eine der Stärken der Integration von QML und C++ ist die Möglichkeit, Benutzeroberflächen in QML getrennt von der C++-Logik und dem Datensatz-Backend zu implementieren, was nicht möglich ist, wenn die C++-Seite QML direkt manipuliert. Ein solcher Ansatz macht es auch schwierig, die QML-Benutzeroberfläche zu ändern, ohne dass sich dies auf ihr C++-Gegenstück auswirkt.

Zugriff auf Mitglieder eines QML-Objekttyps von C++ aus

Eigenschaften

Alle in einem QML-Objekt deklarierten Eigenschaften sind automatisch von C++ aus zugänglich. Gegeben ein QML-Objekt wie dieses:

// MyItem.qml
import QtQuick

Item {
    property int someNumber: 100
}

Der Wert der Eigenschaft someNumber kann mit QQmlProperty oder QObject::setProperty() und QObject::property() gesetzt und gelesen werden:

QQmlEngine engine;QQmlComponent component(&engine, "MyItem.qml");QObject *object = component.create();
qDebug() << "Property value:" << QQmlProperty::read(object, "someNumber").toInt();
QQmlProperty::write(object, "someNumber", 5000);
qDebug() << "Property value:" << object->property("someNumber").toInt();
object->setProperty("someNumber", 100);

Sie sollten immer QObject::setProperty(), QQmlProperty oder QMetaProperty::write() verwenden, um den Wert einer QML-Eigenschaft zu ändern, um sicherzustellen, dass die QML-Engine über die Änderung der Eigenschaft informiert wird. Angenommen, Sie haben einen benutzerdefinierten Typ PushButton mit einer Eigenschaft buttonText, die intern den Wert einer Mitgliedsvariablen m_buttonText widerspiegelt. Die Mitgliedsvariable auf diese Weise direkt zu ändern, ist keine gute Idee:

//bad code
QQmlComponent component(engine, "MyButton.qml");
PushButton *button = qobject_cast<PushButton*>(component.create());
button->m_buttonText = "Click me";

Da der Wert direkt geändert wird, umgeht dies das Meta-Objektsystem von Qt und die QML-Engine wird nicht über die Änderung der Eigenschaft informiert. Das bedeutet, dass die Eigenschaftsbindungen an buttonText nicht aktualisiert werden und alle onButtonTextChanged Handler nicht aufgerufen werden.

Aufrufen von QML-Methoden

Alle QML-Methoden sind dem Meta-Objektsystem ausgesetzt und können von C++ aus mit QMetaObject::invokeMethod() aufgerufen werden. Sie können Typen für die Parameter und den Rückgabewert nach dem Doppelpunkt angeben, wie im folgenden Codeausschnitt gezeigt. Dies kann z. B. nützlich sein, wenn Sie ein Signal in C++ mit einer bestimmten Signatur mit einer in QML definierten Methode verbinden wollen. Wenn Sie die Typen weglassen, wird die C++-Signatur QVariant verwenden.

Hier ist eine C++-Anwendung, die eine QML-Methode mit QMetaObject::invokeMethod() aufruft:

QML
// MyItem.qml
import QtQuick

Item {
    function myQmlFunction(msg: string) : string {
        console.log("Got message:", msg)
        return "some return value"
    }
}
C++
// main.cppQQmlEngine engine;QQmlComponent component(&engine, "MyItem.qml");QObject *object = component.create();QString returnedValue;QString msg = "Hallo aus C++";QMetaObject::invokeMethod(object, "myQmlFunction",Q_RETURN_ARG(QString, returnedValue),Q_ARG(QString, msg));
qDebug() << "QML function returned:" << returnedValue;
Objekt löschen;

Beachten Sie den nach dem Doppelpunkt angegebenen Parameter- und Rückgabetyp. Sie können Werttypen und Objekttypen als Typnamen verwenden.

Wenn der Typ weggelassen oder als var in QML angegeben wird, müssen Sie QVariant als Typ mit Q_RETURN_ARG() und Q_ARG() beim Aufruf von QMetaObject::invokeMethod übergeben.

Verbinden mit QML-Signalen

Alle QML-Signale sind automatisch für C++ verfügbar und können mit QObject::connect() wie jedes gewöhnliche Qt C++-Signal verbunden werden. Im Gegenzug kann jedes C++-Signal von einem QML-Objekt mit Hilfe von Signalhandlern empfangen werden.

Hier ist eine QML-Komponente mit einem Signal namens qmlSignal, das mit einem Parameter vom Typ String ausgegeben wird. Dieses Signal ist mit dem Slot eines C++-Objekts über QObject::connect() verbunden, so dass die Methode cppSlot() immer dann aufgerufen wird, wenn das Signal qmlSignal ausgegeben wird:

// MyItem.qml
import QtQuick

Item {
    id: item
    width: 100; height: 100

    signal qmlSignal(msg: string)

    MouseArea {
        anchors.fill: parent
        onClicked: item.qmlSignal("Hello from QML")
    }
}
class MyClass : public QObject
{ Q_OBJECTpublic slots: void cppSlot(const QString &msg) {        qDebug() << "Called the C++ slot with message:" << msg;
    } };int main(int argc, char *argv[]) { QGuiApplication app(argc, argv);    QQuickView view(QUrl::fromLocalFile("MyItem.qml"));    QObject *item = view.rootObject(); MyClass myClass;    QObject::connect(item, SIGNAL(qmlSignal(QString)), &myClass, SLOT(cppSlot(QString))); view.show(); return app.exec(); }

Ein QML-Objekttyp in einem Signalparameter wird in einen Zeiger auf die Klasse in C++ übersetzt:

// MyItem.qml
import QtQuick 2.0

Item {
    id: item
    width: 100; height: 100

    signal qmlSignal(anObject: Item)

    MouseArea {
        anchors.fill: parent
        onClicked: item.qmlSignal(item)
    }
}
class MyClass : public QObject
{ Q_OBJECTpublic slots: void cppSlot(QQuickItem *item) {       qDebug() << "Called the C++ slot with item:" << item;

       qDebug() << "Item dimensions:" << item->width()
               <<  item->height(); } };int main(int argc, char *argv[]) { QGuiApplication app(argc, argv);    QQuickView view(QUrl::fromLocalFile("MeinElement.qml"));    QObject *item = view.rootObject(); MyClass myClass;    QObject::connect(item, SIGNAL(qmlSignal(QVariant)), &myClass, SLOT(cppSlot(QVariant))); view.show(); return app.exec(); }

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