Schreiben fortgeschrittener QML-Erweiterungen mit C++

Geburtstagsparty Basisprojekt

extending-qml-advanced/advanced1-Base-project

Dieses Tutorial verwendet das Beispiel einer Geburtstagsparty, um einige der Funktionen von QML zu demonstrieren. Der Code für die verschiedenen Funktionen, die im Folgenden erläutert werden, basiert auf diesem Geburtstagsparty-Projekt und stützt sich auf einen Teil des Materials aus dem ersten Tutorial über QML-Erweiterungen. Dieses einfache Beispiel wird dann erweitert, um die verschiedenen QML-Erweiterungen zu veranschaulichen, die im Folgenden erläutert werden. Den vollständigen Code für jede neue Erweiterung des Codes finden Sie in den Tutorials an der unter dem Titel des jeweiligen Abschnitts angegebenen Stelle oder indem Sie dem Link zum Code ganz am Ende dieser Seite folgen.

Das Basisprojekt definiert die Klasse Person und die Klasse BirthdayParty, die die Teilnehmer bzw. die Party selbst modellieren.

class Person : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged FINAL)
    Q_PROPERTY(int shoeSize READ shoeSize WRITE setShoeSize NOTIFY shoeSizeChanged FINAL)
    QML_ELEMENT
    ...
    QString m_name;
    int m_shoeSize = 0;
};

class BirthdayParty : public QObject
{
    Q_OBJECT
    Q_PROPERTY(Person *host READ host WRITE setHost NOTIFY hostChanged FINAL)
    Q_PROPERTY(QQmlListProperty<Person> guests READ guests NOTIFY guestsChanged FINAL)
    QML_ELEMENT
    ...
    Person *m_host = nullptr;
    QList<Person *> m_guests;
};

Alle Informationen über die Party können dann in der entsprechenden QML-Datei gespeichert werden.

BirthdayParty {
    host: Person {
        name: "Bob Jones"
        shoeSize: 12
    }
    guests: [
        Person { name: "Leo Hodges" },
        Person { name: "Jack Smith" },
        Person { name: "Anne Brown" }
    ]
}

Die Datei main.cpp erstellt eine einfache Shell-Anwendung, die anzeigt, wer Geburtstag hat und wer zu seiner Party eingeladen ist.

    QQmlEngine engine;
    QQmlComponent component(&engine);
    component.loadFromModule("People", "Main");
    std::unique_ptr<BirthdayParty> party{ qobject_cast<BirthdayParty *>(component.create()) };

Die Anwendung gibt die folgende Zusammenfassung der Party aus.

"Bob Jones" is having a birthday!
They are inviting:
    "Leo Hodges"
    "Jack Smith"
    "Anne Brown"

In den folgenden Abschnitten wird erläutert, wie man mit Hilfe von Vererbung und Zwang Unterstützung für Boy und Girl Teilnehmer statt nur Person hinzufügt, wie man Standardeigenschaften verwendet, um Teilnehmer der Party implizit als Gäste zuzuordnen, wie man Eigenschaften als Gruppen statt einzeln zuordnet, wie man angehängte Objekte verwendet, um die Antworten der eingeladenen Gäste zu verfolgen, wie man eine Eigenschaftswertquelle verwendet, um den Text des Geburtstagsliedes im Laufe der Zeit anzuzeigen, und wie man Objekte von Drittanbietern für QML zugänglich macht.

Vererbung und Erzwingung

extending-qml-advanced/advanced2-Inheritance-and-coercion

Im Moment wird jeder Teilnehmer als eine Person modelliert. Das ist etwas zu allgemein und es wäre schön, wenn man mehr über die Teilnehmer wissen könnte. Indem wir sie als Jungen und Mädchen spezialisieren, können wir bereits eine bessere Vorstellung davon bekommen, wer kommt.

Zu diesem Zweck werden die Klassen Boy und Girl eingeführt, die beide von Person erben.

class Boy : public Person
{
    Q_OBJECT
    QML_ELEMENT
public:
    using Person::Person;
};

class Girl : public Person
{
    Q_OBJECT
    QML_ELEMENT
public:
    using Person::Person;
};

Die Klasse Person bleibt unverändert und die C++-Klassen Boy und Girl sind triviale Erweiterungen dieser Klasse. Die Typen und ihr QML-Name werden bei der QML-Engine unter QML_ELEMENT registriert.

Beachten Sie, dass die Eigenschaften host und guests in BirthdayParty weiterhin Instanzen von Person annehmen.

class BirthdayParty : public QObject
{
    Q_OBJECT
    Q_PROPERTY(Person *host READ host WRITE setHost NOTIFY hostChanged FINAL)
    Q_PROPERTY(QQmlListProperty<Person> guests READ guests NOTIFY guestsChanged FINAL)
    QML_ELEMENT
    ...
};

Die Implementierung der Klasse Person selbst wurde nicht geändert. Da jedoch die Klasse Person als gemeinsame Basis für Boy und Girl wiederverwendet wurde, sollte Person nicht mehr direkt von QML aus instanzierbar sein. Stattdessen sollte eine explizite Boy oder Girl instanziiert werden.

class Person : public QObject
{
    ...
    QML_ELEMENT
    QML_UNCREATABLE("Person is an abstract base class.")
    ...
};

Während wir die Instanziierung von Person aus QML heraus verbieten wollen, muss es dennoch bei der QML-Engine registriert werden, damit es als Eigenschaftstyp verwendet werden kann und andere Typen zu ihm gezwungen werden können. Dies ist die Aufgabe des Makros QML_UNCREATABLE. Da alle drei Typen, Person, Boy und Girl, im QML-System registriert wurden, wandelt QML bei der Zuweisung die Objekte Boy und Girl automatisch (und typsicher) in Person um.

Mit diesen Änderungen können wir nun die Geburtstagsparty mit den zusätzlichen Informationen über die Teilnehmer wie folgt spezifizieren.

BirthdayParty {
    host: Boy {
        name: "Bob Jones"
        shoeSize: 12
    }
    guests: [
        Boy { name: "Leo Hodges" },
        Boy { name: "Jack Smith" },
        Girl { name: "Anne Brown" }
    ]
}

Standard-Eigenschaften

extending-qml-advanced/advanced3-Default-properties

Derzeit wird in der QML-Datei jede Eigenschaft explizit zugewiesen. Zum Beispiel wird der Eigenschaft host eine Boy und der Eigenschaft guests eine Liste von Boy oder Girl zugewiesen. Das ist einfach, kann aber für diesen speziellen Anwendungsfall etwas vereinfacht werden. Anstatt die Eigenschaft guests explizit zuzuweisen, können wir die Objekte Boy und Girl direkt innerhalb der Gruppe hinzufügen und sie implizit guests zuweisen. Es macht Sinn, dass alle Teilnehmer, die wir angeben und die nicht der Gastgeber sind, Gäste sind. Diese Änderung ist rein syntaktisch, aber sie kann in vielen Situationen ein natürlicheres Gefühl vermitteln.

Die Eigenschaft guests kann als Standardeigenschaft von BirthdayParty festgelegt werden. Das bedeutet, dass jedes Objekt, das innerhalb von BirthdayParty erstellt wird, implizit an die Standardeigenschaft guests angehängt wird. Das resultierende QML sieht wie folgt aus.

BirthdayParty {
    host: Boy {
        name: "Bob Jones"
        shoeSize: 12
    }

    Boy { name: "Leo Hodges" }
    Boy { name: "Jack Smith" }
    Girl { name: "Anne Brown" }
}

Die einzige Änderung, die erforderlich ist, um dieses Verhalten zu aktivieren, ist das Hinzufügen der Annotation DefaultProperty class info zu BirthdayParty, um guests als Standardeigenschaft festzulegen.

class BirthdayParty : public QObject
{
    Q_OBJECT
    Q_PROPERTY(Person *host READ host WRITE setHost NOTIFY hostChanged FINAL)
    Q_PROPERTY(QQmlListProperty<Person> guests READ guests NOTIFY guestsChanged FINAL)
    Q_CLASSINFO("DefaultProperty", "guests")
    QML_ELEMENT
    ...
};

Vielleicht ist Ihnen dieser Mechanismus bereits bekannt. Die Standardeigenschaft für alle Abkömmlinge von Item in QML ist die Eigenschaft data. Alle Elemente, die nicht explizit zu einer Eigenschaft von Item hinzugefügt werden, werden zu data hinzugefügt. Dies macht die Struktur klar und reduziert unnötiges Rauschen im Code.

Gruppierte Eigenschaften

extending-qml-advanced/advanced4-Grouped-properties

Wir brauchen mehr Informationen über die Schuhe der Gäste. Neben der Größe wollen wir auch die Farbe, die Marke und den Preis der Schuhe speichern. Diese Informationen werden in einer ShoeDescription Klasse gespeichert.

class ShoeDescription : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int size READ size WRITE setSize NOTIFY shoeChanged FINAL)
    Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY shoeChanged FINAL)
    Q_PROPERTY(QString brand READ brand WRITE setBrand NOTIFY shoeChanged FINAL)
    Q_PROPERTY(qreal price READ price WRITE setPrice NOTIFY shoeChanged FINAL)
    ...
};

Jede Person hat nun zwei Eigenschaften, eine name und eine Schuhbeschreibung shoe.

class Person : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged FINAL)
    Q_PROPERTY(ShoeDescription *shoe READ shoe WRITE setShoe NOTIFY shoeChanged FINAL)
    ...
};

Die Angabe der Werte für jedes Element der Schuhbeschreibung funktioniert zwar, ist aber ein wenig repetitiv.

    Girl {
        name: "Anne Brown"
        shoe.size: 7
        shoe.color: "red"
        shoe.brand: "Job Macobs"
        shoe.price: 99.99
    }

Gruppierte Eigenschaften bieten eine elegantere Möglichkeit, diese Eigenschaften zuzuweisen. Anstatt die Werte jeder Eigenschaft einzeln zuzuweisen, können die einzelnen Werte als Gruppe an die Eigenschaft shoe übergeben werden, wodurch der Code lesbarer wird. Es sind keine Änderungen erforderlich, um diese Funktion zu aktivieren, da sie standardmäßig für alle QML-Eigenschaften verfügbar ist.

    host: Boy {
        name: "Bob Jones"
        shoe { size: 12; color: "white"; brand: "Bikey"; price: 90.0 }
    }

Angehängte Eigenschaften

extending-qml-advanced/advanced5-Attached-properties

Der Zeitpunkt ist gekommen, an dem der Gastgeber Einladungen verschickt. Um zu wissen, welche Gäste auf die Einladung geantwortet haben und wann, müssen wir diese Informationen irgendwo speichern. Die Speicherung im BirthdayParty Objekt selbst würde nicht wirklich passen. Ein besserer Weg wäre, die Antworten als angehängte Objekte an das Party-Objekt zu speichern.

Zuerst deklarieren wir die Klasse BirthdayPartyAttached, die die Antworten der Gäste enthält.

class BirthdayPartyAttached : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QDate rsvp READ rsvp WRITE setRsvp NOTIFY rsvpChanged FINAL)
    QML_ANONYMOUS
    ...
};

Dann binden wir sie an die Klasse BirthdayParty an und definieren qmlAttachedProperties(), um das angehängte Objekt zurückzugeben.

class BirthdayParty : public QObject
{
    ...
    QML_ATTACHED(BirthdayPartyAttached)
    ...
    static BirthdayPartyAttached *qmlAttachedProperties(QObject *);

};

Die angehängten Objekte können nun in QML verwendet werden, um die rsvp-Informationen der eingeladenen Gäste zu speichern.

BirthdayParty {
    Boy {
        name: "Robert Campbell"
        BirthdayParty.rsvp: Date.fromLocaleString(Qt.locale(), "2023-03-01", "yyyy-MM-dd")
    }

    Boy {
        name: "Leo Hodges"
        shoe { size: 10; color: "black"; brand: "Reebok"; price: 59.95 }
        BirthdayParty.rsvp: Date.fromLocaleString(Qt.locale(), "2023-03-03", "yyyy-MM-dd")
    }

    host: Boy {
        name: "Jack Smith"
        shoe { size: 8; color: "blue"; brand: "Puma"; price: 19.95 }
    }
}

Schließlich kann auf diese Informationen wie folgt zugegriffen werden.

            QDate rsvpDate;
            QObject *attached = qmlAttachedPropertiesObject<BirthdayParty>(guest, false);

            if (attached)
                rsvpDate = attached->property("rsvp").toDate();

Das Programm gibt die folgende Zusammenfassung der bevorstehenden Party aus.

"Jack Smith" is having a birthday!
He is inviting:
    "Robert Campbell" RSVP date: "Wed Mar 1 2023"
    "Leo Hodges" RSVP date: "Mon Mar 6 2023"

Eigenschaft Wert Quelle

extending-qml-advanced/advanced6-Property-value-source

Während der Party müssen die Gäste für den Gastgeber singen. Es wäre praktisch, wenn das Programm die Liedtexte anzeigen könnte, die auf den Anlass zugeschnitten sind, um den Gästen zu helfen. Zu diesem Zweck wird eine Eigenschaftswertquelle verwendet, um die Strophen des Liedes im Laufe der Zeit zu erzeugen.

class HappyBirthdaySong : public QObject, public QQmlPropertyValueSource
{
    Q_OBJECT
    Q_INTERFACES(QQmlPropertyValueSource)
    ...
    void setTarget(const QQmlProperty &) override;

};

Die Klasse HappyBirthdaySong wird als Wertquelle hinzugefügt. Sie muss von QQmlPropertyValueSource erben und die Schnittstelle QQmlPropertyValueSource mit dem Makro Q_INTERFACES implementieren. Die Funktion setTarget() wird verwendet, um zu definieren, auf welche Eigenschaft diese Quelle wirkt. In diesem Fall schreibt die Wertequelle in die Eigenschaft announcement der BirthdayParty, um den Liedtext über die Zeit anzuzeigen. Sie verfügt über einen internen Zeitgeber, der bewirkt, dass die Eigenschaft announcement der Partei wiederholt auf die nächste Zeile des Liedtextes gesetzt wird.

In QML wird eine HappyBirthdaySong innerhalb der BirthdayParty instanziiert. Das Schlüsselwort on in der Signatur wird verwendet, um die Eigenschaft anzugeben, auf die die Wertquelle abzielt, in diesem Fall announcement. Die Eigenschaft name des HappyBirthdaySong Objekts ist ebenfalls an den Namen des Gastgebers der Party gebunden.

BirthdayParty {
    id: party
    HappyBirthdaySong on announcement {
        name: party.host.name
    }
    ...
}

Das Programm zeigt die Uhrzeit an, zu der die Party begonnen hat, indem es das Signal partyStarted verwendet, und gibt dann immer wieder die folgenden Happy-Birthday-Verse aus.

Happy birthday to you,
Happy birthday to you,
Happy birthday dear Bob Jones,
Happy birthday to you!

Integration fremder Objekte

extending-qml-advanced/advanced7-Foreign-objects-integration

Anstatt den Text nur auf der Konsole auszugeben, möchten die Teilnehmer eine etwas ausgefallenere Anzeige mit Unterstützung für Farben verwenden. Sie würden es gerne in das Projekt integrieren, aber derzeit ist es nicht möglich, den Bildschirm über QML zu konfigurieren, da er aus einer Bibliothek eines Drittanbieters stammt. Um dieses Problem zu lösen, müssen die erforderlichen Typen der QML-Engine zugänglich gemacht werden, damit ihre Eigenschaften direkt in QML geändert werden können.

Die Anzeige kann durch die Klasse ThirdPartyDisplay gesteuert werden. Sie verfügt über Eigenschaften zur Definition des Inhalts und der Vorder- und Hintergrundfarben des anzuzeigenden Textes.

class Q_DECL_EXPORT ThirdPartyDisplay : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString content READ content WRITE setContent NOTIFY contentChanged FINAL)
    Q_PROPERTY(QColor foregroundColor READ foregroundColor WRITE setForegroundColor NOTIFY colorsChanged FINAL)
    Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor NOTIFY colorsChanged FINAL)
    ...
};

Um diesen Typ für QML zugänglich zu machen, können wir ihn bei der Engine mit QML_ELEMENT registrieren. Da die Klasse jedoch nicht für Änderungen zugänglich ist, kann QML_ELEMENT nicht einfach hinzugefügt werden. Um den Typ bei der Engine zu registrieren, muss der Typ von außen registriert werden. Hierfür ist QML_FOREIGN gedacht. Wenn sie in einem Typ in Verbindung mit anderen QML-Makros verwendet werden, gelten die anderen Makros nicht für den Typ, in dem sie sich befinden, sondern für den durch QML_FOREIGN bezeichneten Fremdtyp.

class ForeignDisplay : public QObject
{
    Q_OBJECT
    QML_NAMED_ELEMENT(ThirdPartyDisplay)
    QML_FOREIGN(ThirdPartyDisplay)
};

Auf diese Weise hat die BirthdayParty jetzt eine neue Eigenschaft mit der Anzeige.

class BirthdayParty : public QObject
{
    Q_OBJECT
    Q_PROPERTY(Person *host READ host WRITE setHost NOTIFY hostChanged FINAL)
    Q_PROPERTY(QQmlListProperty<Person> guests READ guests NOTIFY guestsChanged FINAL)
    Q_PROPERTY(QString announcement READ announcement WRITE setAnnouncement NOTIFY announcementChanged FINAL)
    Q_PROPERTY(ThirdPartyDisplay *display READ display WRITE setDisplay NOTIFY displayChanged FINAL)
    ...
};

Und in QML können die Farben des Textes auf dem schicken dritten Display explizit eingestellt werden.

BirthdayParty {
    display: ThirdPartyDisplay {
        foregroundColor: "black"
        backgroundColor: "white"
    }
    ...
}

Das Setzen der Eigenschaft announcement der BirthdayParty sendet nun die Nachricht an das schicke Display, anstatt sie selbst zu drucken.

void BirthdayParty::setAnnouncement(const QString &announcement)
{
    if (m_announcement != announcement) {
        m_announcement = announcement;
        emit announcementChanged();
    }
    m_display->setContent(announcement);
}

Die Ausgabe sieht dann immer wieder so aus, ähnlich wie im vorherigen Abschnitt.

[Fancy ThirdPartyDisplay] Happy birthday to you,
[Fancy ThirdPartyDisplay] Happy birthday to you,
[Fancy ThirdPartyDisplay] Happy birthday dear Bob Jones,
[Fancy ThirdPartyDisplay] Happy birthday to you!

Siehe auch Festlegen von Standard- und übergeordneten Eigenschaften für QML-Objekttypen, Gruppierte Eigenschaften, Bereitstellen von angehängten Eigenschaften, Quellen für Eigenschaftswerte und Registrieren fremder Typen.

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