Écrire des extensions QML avancées avec C++
Projet de base « BirthdayParty »
extending-qml-advanced/advanced1-Base-project
Ce tutoriel utilise l’exemple d’une fête d’anniversaire pour illustrer certaines fonctionnalités de QML. Le code correspondant aux différentes fonctionnalités expliquées ci-dessous s’appuie sur ce projet de fête d’anniversaire et reprend certains éléments du premier tutoriel consacré aux extensions QML. Cet exemple simple est ensuite développé pour illustrer les différentes extensions QML expliquées ci-dessous. Le code complet de chaque nouvelle extension peut être consulté dans les tutoriels à l'emplacement indiqué sous le titre de chaque section ou en suivant le lien vers le code situé tout à la fin de cette page.
Le projet de base définit les classes ` Person ` et ` BirthdayParty `, qui modélisent respectivement les invités et la fête elle-même.
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;
};Toutes les informations relatives à la fête peuvent ensuite être stockées dans le fichier QML correspondant.
BirthdayParty {
host: Person {
name: "Bob Jones"
shoeSize: 12
}
guests: [
Person { name: "Leo Hodges" },
Person { name: "Jack Smith" },
Person { name: "Anne Brown" }
]
}Le fichier main.cpp crée une application shell simple qui affiche de qui est l'anniversaire et qui est invité à la fête.
QQmlEngine engine;
QQmlComponent component(&engine);
component.loadFromModule("People", "Main");
std::unique_ptr<BirthdayParty> party{ qobject_cast<BirthdayParty *>(component.create()) };L'application affiche le résumé suivant de la fête.
"Bob Jones" is having a birthday!
They are inviting:
"Leo Hodges"
"Jack Smith"
"Anne Brown"Les sections suivantes expliquent comment ajouter la prise en charge des objets ` Boy ` et ` Girl ` (invités) à la place de la simple classe ` Person ` en utilisant l'héritage et la coercition, comment utiliser les propriétés par défaut pour attribuer implicitement aux participants à la fête le statut d'invités, comment attribuer des propriétés par groupes plutôt qu'une par une, comment utiliser des objets associés pour suivre les réponses des invités, comment utiliser une source de valeur de propriété pour afficher les paroles de la chanson « Joyeux anniversaire » au fil du temps, et comment exposer des objets tiers à QML.
Héritage et coercition
extending-qml-advanced/advanced2-Inheritance-and-coercion
Pour l’instant, chaque participant est modélisé comme une personne. C’est un peu trop générique et il serait intéressant d’en savoir davantage sur les participants. En les spécialisant en garçons et en filles, nous pouvons déjà nous faire une meilleure idée de qui vient.
Pour ce faire, les classes ` Boy ` et ` Girl ` sont introduites ; elles héritent toutes deux de ` Person`.
class Boy : public Person
{
Q_OBJECT
QML_ELEMENT
public:
using Person::Person;
};
class Girl : public Person
{
Q_OBJECT
QML_ELEMENT
public:
using Person::Person;
};La classe ` Person ` reste inchangée et les classes C++ ` Boy ` et ` Girl ` en sont des extensions simples. Les types et leurs noms QML sont enregistrés auprès du moteur QML à l’aide de ` QML_ELEMENT`.
Notez que les propriétés host et guests de BirthdayParty acceptent toujours des instances de Person.
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
...
};L’implémentation de la classe Person elle-même n’a pas été modifiée. Cependant, la classe Person ayant été réutilisée comme base commune pour Boy et Girl, Person ne devrait plus pouvoir être instanciée directement depuis QML. Il convient plutôt d’instancier explicitement Boy ou Girl.
class Person : public QObject
{
...
QML_ELEMENT
QML_UNCREATABLE("Person is an abstract base class.")
...
};Bien que nous souhaitions interdire l’instanciation de ` Person ` depuis le QML, ce type doit tout de même être enregistré auprès du moteur QML afin de pouvoir être utilisé comme type de propriété et de permettre la conversion d’autres types vers celui-ci. C’est ce que fait la macro ` QML_UNCREATABLE `. Comme les trois types, Person, Boy et Girl, ont été enregistrés auprès du système QML, lors d’une affectation, QML convertit automatiquement (et en toute sécurité de type) les objets Boy et Girl en Person.
Grâce à ces modifications, nous pouvons désormais définir la fête d’anniversaire en ajoutant des informations supplémentaires sur les invités, comme suit.
BirthdayParty {
host: Boy {
name: "Bob Jones"
shoeSize: 12
}
guests: [
Boy { name: "Leo Hodges" },
Boy { name: "Jack Smith" },
Girl { name: "Anne Brown" }
]
}Propriétés par défaut
extending-qml-advanced/advanced3-Default-properties
Actuellement, dans le fichier QML, chaque propriété est attribuée explicitement. Par exemple, la propriété host se voit attribuer un Boy et la propriété guests se voit attribuer une liste de Boy ou de Girl. C’est simple, mais cela peut être encore un peu simplifié pour ce cas d’utilisation spécifique. Au lieu d’attribuer explicitement la propriété ` guests `, nous pouvons ajouter directement des objets ` Boy ` et ` Girl ` au sein de la « party » et les faire attribuer implicitement à ` guests `. Il est logique que tous les participants que nous spécifions, et qui ne sont pas l’hôte, soient des invités. Ce changement est purement syntaxique, mais il peut apporter plus de naturel dans de nombreuses situations.
La propriété guests peut être désignée comme propriété par défaut de BirthdayParty. Cela signifie que chaque objet créé au sein d’un BirthdayParty est implicitement ajouté à la propriété par défaut guests. Le code QML obtenu se présente comme suit.
BirthdayParty {
host: Boy {
name: "Bob Jones"
shoeSize: 12
}
Boy { name: "Leo Hodges" }
Boy { name: "Jack Smith" }
Girl { name: "Anne Brown" }
}La seule modification nécessaire pour activer ce comportement consiste à ajouter l’annotation d’information de classe DefaultProperty à BirthdayParty afin de désigner guests comme sa propriété par défaut.
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
...
};Vous connaissez peut-être déjà ce mécanisme. La propriété par défaut pour tous les descendants de ` Item ` en QML est la propriété ` data `. Tous les éléments qui ne sont pas explicitement ajoutés à une propriété d’un ` Item ` seront ajoutés à ` data`. Cela clarifie la structure et réduit le bruit inutile dans le code.
Propriétés groupées
extending-qml-advanced/advanced4-Grouped-properties
Nous avons besoin de plus d’informations sur les chaussures des invités. Outre leur pointure, nous souhaitons également stocker la couleur, la marque et le prix des chaussures. Ces informations sont stockées dans une classe ShoeDescription.
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)
...
};Chaque personne dispose désormais de deux propriétés : une « name » et une « shoe description » 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)
...
};Spécifier les valeurs pour chaque élément de la description de la chaussure fonctionne, mais s’avère un peu répétitif.
Girl {
name: "Anne Brown"
shoe.size: 7
shoe.color: "red"
shoe.brand: "Job Macobs"
shoe.price: 99.99
}Les propriétés groupées offrent un moyen plus élégant d’attribuer ces propriétés. Au lieu d’attribuer les valeurs à chaque propriété une par une, les valeurs individuelles peuvent être transmises en bloc à la propriété « shoe », ce qui rend le code plus lisible. Aucune modification n’est nécessaire pour activer cette fonctionnalité, car elle est disponible par défaut dans tout le QML.
host: Boy {
name: "Bob Jones"
shoe { size: 12; color: "white"; brand: "Bikey"; price: 90.0 }
}Propriétés associées
extending-qml-advanced/advanced5-Attached-properties
Le moment est venu pour l’hôte d’envoyer les invitations. Afin de savoir quels invités ont répondu à l’invitation et à quel moment, nous avons besoin d’un emplacement où stocker ces informations. Les stocker dans l’objet ` BirthdayParty ` lui-même ne serait pas vraiment adapté. Une meilleure solution serait de stocker les réponses en tant qu’objets associés à l’objet `party`.
Tout d’abord, nous déclarons la classe ` BirthdayPartyAttached ` qui contient les réponses des invités.
class BirthdayPartyAttached : public QObject
{
Q_OBJECT
Q_PROPERTY(QDate rsvp READ rsvp WRITE setRsvp NOTIFY rsvpChanged FINAL)
QML_ANONYMOUS
...
};Nous l’associons ensuite à la classe ` BirthdayParty ` et définissons la méthode ` qmlAttachedProperties() ` pour qu’elle renvoie l’objet associé.
class BirthdayParty : public QObject
{
...
QML_ATTACHED(BirthdayPartyAttached)
...
static BirthdayPartyAttached *qmlAttachedProperties(QObject *);
};Désormais, les objets associés peuvent être utilisés dans le QML pour stocker les informations de réponse des invités.
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 }
}
}Enfin, ces informations sont accessibles de la manière suivante.
QDate rsvpDate;
QObject *attached = qmlAttachedPropertiesObject<BirthdayParty>(guest, false);
if (attached)
rsvpDate = attached->property("rsvp").toDate();Le programme affiche le résumé suivant concernant la fête à venir.
"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"Source de valeur de propriété
extending-qml-advanced/advanced6-Property-value-source
Pendant la fête, les invités doivent chanter pour l'hôte. Il serait pratique que le programme puisse afficher les paroles adaptées à l'occasion pour aider les invités. À cette fin, une source de valeur de propriété est utilisée pour générer les couplets de la chanson au fur et à mesure.
class HappyBirthdaySong : public QObject, public QQmlPropertyValueSource
{
Q_OBJECT
Q_INTERFACES(QQmlPropertyValueSource)
...
void setTarget(const QQmlProperty &) override;
};La classe HappyBirthdaySong est ajoutée en tant que source de valeur. Elle doit hériter de QQmlPropertyValueSource et implémenter l’interface QQmlPropertyValueSource à l’aide de la macro Q_INTERFACES. La fonction setTarget() est utilisée pour définir sur quelle propriété cette source agit. Dans ce cas, la source de valeur écrit dans la propriété announcement de l’ BirthdayParty afin d’afficher les paroles au fil du temps. Elle dispose d’un minuteur interne qui fait en sorte que la propriété announcement de l’ soit définie de manière répétée sur la ligne suivante des paroles.
En QML, une source de valeur ( HappyBirthdaySong ) est instanciée à l’intérieur de la source de données ( BirthdayParty). Le mot-clé « on » dans sa signature sert à spécifier la propriété ciblée par la source de valeur, en l’occurrence « announcement ». La propriété « name » de l’objet « HappyBirthdaySong » est également liée au nom de l’hôte de la fête.
BirthdayParty {
id: party
HappyBirthdaySong on announcement {
name: party.host.name
}
...
}Le programme affiche l’heure à laquelle la fête a commencé à l’aide du signal partyStarted, puis affiche en boucle les vers suivants pour souhaiter un joyeux anniversaire.
Happy birthday to you,
Happy birthday to you,
Happy birthday dear Bob Jones,
Happy birthday to you!Intégration d’objets externes
extending-qml-advanced/advanced7-Foreign-objects-integration
Au lieu de simplement afficher les paroles sur la console, les participants souhaiteraient utiliser un affichage plus sophistiqué prenant en charge les couleurs. Ils aimeraient l’intégrer au projet, mais il n’est actuellement pas possible de configurer l’écran depuis QML car il provient d’une bibliothèque tierce. Pour résoudre ce problème, les types nécessaires doivent être exposés au moteur QML afin que leurs propriétés puissent être modifiées directement dans QML.
L'affichage peut être contrôlé par la classe ` ThirdPartyDisplay `. Elle dispose de propriétés permettant de définir le contenu ainsi que les couleurs de premier plan et d’arrière-plan du texte à afficher.
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)
...
};Pour exposer ce type à QML, nous pouvons l’enregistrer auprès du moteur à l’aide de ` QML_ELEMENT`. Cependant, comme la classe n’est pas accessible en vue de sa modification, la méthode ` QML_ELEMENT ` ne peut pas simplement y être ajoutée. Pour enregistrer le type auprès du moteur, celui-ci doit être enregistré depuis l’extérieur. C’est à cela que sert ` QML_FOREIGN `. Lorsqu’elle est utilisée dans un type en conjonction avec d’autres macros QML, ces dernières s’appliquent non pas au type dans lequel elles se trouvent, mais au type externe désigné par ` QML_FOREIGN`.
class ForeignDisplay : public QObject
{
Q_OBJECT
QML_NAMED_ELEMENT(ThirdPartyDisplay)
QML_FOREIGN(ThirdPartyDisplay)
};De cette manière, BirthdayParty dispose désormais d’une nouvelle propriété avec l’affichage.
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)
...
};De plus, en QML, les couleurs du texte sur ce troisième affichage sophistiqué peuvent être définies explicitement.
BirthdayParty {
display: ThirdPartyDisplay {
foregroundColor: "black"
backgroundColor: "white"
}
...
}La définition de la propriété ` announcement ` de la classe `BirthdayParty` envoie désormais le message à l’affichage sophistiqué au lieu de l’afficher elle-même.
void BirthdayParty::setAnnouncement(const QString &announcement)
{
if (m_announcement != announcement) {
m_announcement = announcement;
emit announcementChanged();
}
m_display->setContent(announcement);
}Le résultat s’affiche alors de manière répétée, comme dans la section précédente.
[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!Voir également Spécification des propriétés par défaut et des propriétés parentes pour les types d’objets QML, Propriétés groupées, Fourniture de propriétés attachées, Sources de valeurs de propriétés et Enregistrement de types étrangers.
© 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.