Sur cette page

Ecrire 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 démontrer certaines des fonctionnalités de QML. Le code des différentes fonctionnalités expliquées ci-dessous est basé sur ce projet de fête d'anniversaire et s'appuie sur certains éléments du premier tutoriel sur les 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 du code peut être trouvé dans les tutoriels à l'endroit spécifié sous le titre de chaque section ou en suivant le lien vers le code à la toute fin de cette page.

Le projet de base définit les classes Person et BirthdayParty, qui modélisent respectivement les participants 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 simple application shell qui affiche le nom de la personne dont c'est l'anniversaire et qui est invitée à la fête.

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

L'application produit 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 invités Boy et Girl au lieu de Person en utilisant l'héritage et la coercition, comment utiliser les propriétés par défaut pour affecter implicitement les participants à la fête en tant qu'invités, comment affecter les propriétés en tant que groupes au lieu d'une par une, comment utiliser les objets attachés pour garder une trace des 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 accompagnateur est modélisé comme une personne. C'est un peu trop générique et il serait bon de pouvoir en savoir plus sur les participants. En les spécialisant comme des garçons et des filles, nous pouvons déjà avoir une meilleure idée de qui vient.

Pour ce faire, les classes Boy et Girl sont introduites, toutes deux héritant 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 triviales. Les types et leur nom QML sont enregistrés dans le moteur QML à l'adresse QML_ELEMENT.

Notez que les propriétés host et guests dans BirthdayParty prennent 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, comme la classe Person a été réutilisée comme base commune pour Boy et Girl, Person ne devrait plus être instanciable directement à partir de QML. Un Boy ou un Girl explicite devrait être instancié à la place.

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

Bien que nous souhaitions interdire l'instanciation de Person à partir de QML, il est toujours nécessaire de l'enregistrer auprès du moteur QML afin qu'il puisse être utilisé en tant que type de propriété et que d'autres types puissent lui être imposés. 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 de l'affectation, QML convertit automatiquement (et en toute sécurité) les objets Boy et Girl en Person.

Avec ces changements en place, nous pouvons maintenant spécifier la fête d'anniversaire avec les informations supplémentaires sur les participants 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 Girl. C'est facile, mais il est possible de simplifier un peu les choses pour ce cas d'utilisation spécifique. Au lieu d'assigner explicitement la propriété guests, nous pouvons ajouter directement les objets Boy et Girl à l'intérieur de la fête et les assigner 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 donner une impression plus naturelle dans de nombreuses situations.

La propriété guests peut être désignée comme la propriété par défaut de BirthdayParty, ce qui signifie que chaque objet créé à l'intérieur d'une BirthdayParty est implicitement ajouté à la propriété par défaut guests. Le code QML qui en résulte ressemble à ceci.

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

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

La seule modification requise pour activer ce comportement consiste à ajouter l'annotation DefaultProperty class info à BirthdayParty pour 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
    ...
};

Ce mécanisme vous est peut-être déjà familier. La propriété par défaut de 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'une Item seront ajoutés à data. Cela rend la structure claire et réduit les bruits inutiles 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 taille, nous voulons é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 a maintenant deux propriétés, une name et une description de la chaussure 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 de chaque élément de la description de la chaussure fonctionne mais est 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 une manière plus élégante 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 tant que groupe à 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 pour l'ensemble de QML.

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

Propriétés attachées

extending-qml-advanced/advanced5-Attached-properties

Le moment est venu pour l'hôte d'envoyer des invitations. Pour savoir quels invités ont répondu à l'invitation et quand, nous avons besoin d'un endroit où stocker ces informations. La stocker dans l'objet BirthdayParty lui-même ne conviendrait pas vraiment. Une meilleure solution consisterait à stocker les réponses en tant qu'objets attaché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'attachons à la classe BirthdayParty et définissons qmlAttachedProperties() pour renvoyer l'objet attaché.

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

};

Maintenant, les objets attachés peuvent être utilisés dans le QML pour contenir les informations rsvp 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, il est possible d'accéder à ces informations de la manière suivante.

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

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

Le programme produit le résumé suivant de 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"

Propriété Valeur Source

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

Pendant la fête, les invités doivent chanter pour l'hôte. Il serait utile que le programme puisse afficher les paroles personnalisées pour l'occasion afin d'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 fil du temps.

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 avec la macro Q_INTERFACES. La fonction setTarget() est utilisée pour définir la propriété sur laquelle cette source agit. Dans ce cas, la source de valeur écrit dans la propriété announcement de BirthdayParty pour afficher les paroles au fil du temps. Elle dispose d'une minuterie interne qui fait que la propriété announcement de la partie est réglée sur la ligne suivante des paroles de façon répétée.

En QML, un HappyBirthdaySong est instancié à l'intérieur du BirthdayParty. Le mot-clé on dans sa signature est utilisé pour spécifier la propriété que la source de valeur cible, dans ce cas 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 et imprime ensuite les versets de joyeux anniversaire suivants en boucle.

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

Intégration des objets étrangers

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

Au lieu de se contenter d'imprimer les paroles sur la console, les participants aimeraient utiliser un affichage plus sophistiqué prenant en charge les couleurs. Ils aimeraient l'intégrer dans le projet, mais il n'est actuellement pas possible de configurer l'écran à partir de 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 possède des propriétés permettant de définir le contenu et les couleurs d'avant-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 avec QML_ELEMENT. Cependant, comme la classe n'est pas accessible à la modification, QML_ELEMENT ne peut pas lui être simplement ajouté. Pour enregistrer le type auprès du moteur, il faut l'enregistrer de l'extérieur. C'est la raison d'être de QML_FOREIGN. Lorsqu'elles sont utilisées dans un type en conjonction avec d'autres macros QML, les autres macros s'appliquent non pas au type dans lequel elles résident, mais au type étranger désigné par QML_FOREIGN.

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

Ainsi, 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)
    ...
};

Et, en QML, les couleurs du texte sur le troisième affichage fantaisiste peuvent être définies explicitement.

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

La définition de la propriété announcement de BirthdayParty envoie maintenant le message à l'écran fantaisie au lieu de l'imprimer lui-même.

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

La sortie ressemble alors à ceci, encore et encore, 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 aussi Spécification des propriétés par défaut et des propriétés parent 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.