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.