Sur cette page

Exposition des attributs des types C++ vers QML

QML peut facilement être étendu avec des fonctionnalités définies dans du code C++. Grâce à l'intégration étroite du moteur QML avec le système de méta-objets Qt, toute fonctionnalité correctement exposée par une classe dérivée de ` QObject` ou un type ` Q_GADGET ` est accessible depuis le code QML. Cela permet d’accéder directement aux données et aux fonctions C++ depuis le QML, souvent sans modification ou avec très peu de modifications.

Le moteur QML est capable d’introsecter des instances de ` QObject ` via le système de méta-objets. Cela signifie que tout code QML peut accéder aux membres suivants d’une instance d’une classe dérivée de ` QObject` :

  • Propriétés
  • Méthodes (à condition qu’il s’agisse de slots publics ou qu’elles soient marquées avec ` Q_INVOKABLE`)
  • Signaux

(De plus, les énumérations sont disponibles si elles ont été déclarées avec l'Q_ENUM. Voir Conversion des types de données entre QML et C++ pour plus de détails.)

En général, ceux-ci sont accessibles depuis QML, qu’une classe dérivée de ` QObject` ait été enregistrée ou non auprès du système de types QML. Toutefois, si une classe doit être utilisée d’une manière qui nécessite que le moteur accède à des informations de type supplémentaires — par exemple, si la classe elle-même doit être utilisée comme paramètre de méthode ou comme propriété, ou si l’un de ses types d’énumération doit être utilisé de cette manière —, il peut alors s’avérer nécessaire d’enregistrer la classe. L’enregistrement est recommandé pour tous les types que vous utilisez en QML, car seuls les types enregistrés peuvent être analysés lors de la compilation.

L'enregistrement est obligatoire pour les types « Q_GADGET », car ils ne dérivent pas d'une base commune connue et ne peuvent pas être rendus disponibles automatiquement. Sans enregistrement, leurs propriétés et leurs méthodes sont inaccessibles.

Vous pouvez rendre des types C++ provenant d’un autre module disponibles dans votre propre module en ajoutant une dépendance à votre appel ` qt_add_qml_module ` à l’aide de l’option ` DEPENDENCIES `. Vous pouvez, par exemple, souhaiter dépendre de ` QtQuick ` afin que vos types C++ exposés via QML puissent utiliser ` QColor ` comme arguments de méthode et valeurs de retour. ` QtQuick ` expose ` QColor ` en tant que type de valeur « couleur ». De telles dépendances peuvent être déduites automatiquement lors de l’exécution, mais vous ne devez pas vous y fier.

Notez également qu’un certain nombre de concepts importants abordés dans ce document sont illustrés dans le tutoriel Écrire des extensions QML avec C++.

Pour plus d’informations sur le C++ et les différentes méthodes d’intégration avec QML, consultez la page de présentation de l’intégration entre C++ et QML.

Gestion des types de données et propriété

Toute donnée transférée du C++ vers le QML, qu'il s'agisse d'une valeur de propriété, d'un paramètre ou d'une valeur de retour d'une méthode, ou encore d'une valeur de paramètre d'un signal, doit être d'un type pris en charge par le moteur QML.

Par défaut, le moteur prend en charge un certain nombre de types Qt C++ et peut les convertir automatiquement de manière appropriée lorsqu'ils sont utilisés depuis QML. De plus, les classes C++ enregistrées dans le système de types Qml peuvent être utilisées comme types de données, tout comme leurs énumérations si elles sont correctement enregistrées. Voir Conversion des types de données entre Qml et C++ pour plus d'informations.

Par ailleurs, les règles de propriété des données sont prises en compte lors du transfert de données du C++ vers le QML. Voir Propriété des données pour plus de détails.

Exposition des propriétés

Une propriété peut être définie pour toute classe dérivée de ` QObject` à l'aide de la macro ` Q_PROPERTY()`. Une propriété est un membre de données de la classe associé à une fonction de lecture et, éventuellement, à une fonction d'écriture.

Toutes les propriétés d’une classe dérivée de ` QObject` ou d’une classe ` Q_GADGET ` sont accessibles depuis QML.

Par exemple, voici une classe Message dotée d’une propriété author. Comme spécifié par l’appel à la macro Q_PROPERTY, cette propriété est accessible en lecture via la méthode author() et en écriture via la méthode setAuthor():

Remarque : n’utilisez pas ` typedef ` ni `using` pour les types ` Q_PROPERTY `, car cela induirait moc en erreur. Cela pourrait entraîner l’échec de certaines comparaisons de types.

Au lieu de :

using FooEnum = Foo::Enum;

class Bar : public QObject
{
    Q_OBJECT
    Q_PROPERTY(FooEnum enum READ enum WRITE setEnum NOTIFY enumChanged)
};

Faites directement référence au type :

class Bar : public QObject
{
    Q_OBJECT
    Q_PROPERTY(Foo::Enum enum READ enum WRITE setEnum NOTIFY enumChanged)
};

Pour rendre « Message » disponible, vous devez utiliser « QML_ELEMENT » en C++ et « qt_add_qml_module » dans CMake.

class Message : public QObject
{
    Q_OBJECT
    QML_ELEMENT
    Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged)
public:
    void setAuthor(const QString &a)
    {
        if (a != m_author) {
            m_author = a;
            emit authorChanged();
        }
    }

    QString author() const
    {
        return m_author;
    }

signals:
    void authorChanged();

private:
    QString m_author;
};

Une instance de ` Message ` peut être transmise en tant que propriété « required » à un fichier nommé ` MyItem.qml ` pour la rendre disponible :

int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);

    QQuickView view;
    Message msg;
    view.setInitialProperties({{"msg", &msg}});
    view.setSource(QUrl::fromLocalFile("MyItem.qml"));
    view.show();

    return app.exec();
}

La propriété ` author ` peut alors être lue à partir du fichier ` MyItem.qml` :

// MyItem.qml
import QtQuick

Text {
    required property Message msg

    width: 100; height: 100
    text: msg.author    // invokes Message::author() to get this value

    Component.onCompleted: {
        msg.author = "Jonah"  // invokes Message::setAuthor()
    }
}

Pour une interopérabilité maximale avec QML, toute propriété modifiable doit être associée à un signal NOTIFY qui est émis chaque fois que la valeur de la propriété change. Cela permet d'utiliser la propriété avec le liaison de propriétés, une fonctionnalité essentielle de QML qui garantit les relations entre les propriétés en mettant automatiquement à jour une propriété dès qu'une de ses dépendances change de valeur.

Dans l’exemple ci-dessus, le signal NOTIFY associé à la propriété ` author ` est ` authorChanged`, comme spécifié dans l’appel de la macro ` Q_PROPERTY()`. Cela signifie que chaque fois que le signal est émis — comme c’est le cas lorsque la propriété `author` est modifiée dans `Message::setAuthor()` —, cela signale au moteur QML que tous les liaisons impliquant la propriété ` author ` doivent être mises à jour ; à son tour, le moteur mettra à jour la propriété ` text ` en appelant à nouveau ` Message::author() `.

Si la propriété author était modifiable mais ne disposait pas d’un signal NOTIFY associé, la valeur text serait initialisée avec la valeur initiale renvoyée par Message::author(), mais ne serait pas mise à jour en cas de modifications ultérieures de cette propriété. De plus, toute tentative de liaison à cette propriété depuis QML générerait un avertissement d’exécution de la part du moteur.

Remarque : il est recommandé de nommer le signal NOTIFY <property>Changed, où <property> correspond au nom de la propriété. Le gestionnaire de signal de modification de propriété associé généré par le moteur QML prendra toujours la forme on<Property>Changed, quel que soit le nom du signal C++ correspondant ; il est donc recommandé de respecter cette convention de nommage pour éviter toute confusion.

Remarques sur l’utilisation des signaux « Notify »

Pour éviter les boucles ou les évaluations excessives, les développeurs doivent s’assurer que le signal de modification de propriété n’est émis que lorsque la valeur de la propriété a effectivement changé. De plus, si une propriété ou un groupe de propriétés est rarement utilisé, il est permis d’utiliser le même signal NOTIFY pour plusieurs propriétés. Cette pratique doit être mise en œuvre avec prudence afin de ne pas nuire aux performances.

La présence d’un signal NOTIFY entraîne une légère surcharge. Il arrive que la valeur d’une propriété soit définie lors de la construction de l’objet et qu’elle ne change pas par la suite. Le cas le plus courant est celui d’une propriété en lecture seule contenant un sous-objet, généralement accessible via la syntaxe des propriétés groupées, où le sous-objet est alloué une seule fois et n’est libéré que lorsque le propriétaire est supprimé. Dans ces cas, l’attribut CONSTANT peut être ajouté à la déclaration de la propriété à la place d’un signal NOTIFY.

L’attribut CONSTANT ne doit être utilisé que pour les propriétés dont la valeur est définie et finalisée uniquement dans le constructeur de la classe. Toutes les autres propriétés destinées à être utilisées dans des liaisons doivent plutôt comporter un signal NOTIFY.

Propriétés de type objet

Les propriétés de type objet sont accessibles depuis QML à condition que le type d'objet ait été correctement enregistré dans le système de types QML.

Par exemple, le type ` Message ` peut disposer d'une propriété ` body ` de type ` MessageBody*` :

class Message : public QObject
{
    Q_OBJECT
    Q_PROPERTY(MessageBody* body READ body WRITE setBody NOTIFY bodyChanged)
public:
    MessageBody* body() const;
    void setBody(MessageBody* body);
};

class MessageBody : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString text READ text WRITE text NOTIFY textChanged)
// ...
}

Supposons que le type ` Message ` ait été enregistré dans le système de types QML, ce qui permet de l'utiliser comme type d'objet dans le code QML :

Message {
    // ...
}

Si le type ` MessageBody ` était également enregistré dans le système de types, il serait possible d’attribuer ` MessageBody ` à la propriété ` body ` d’un ` Message`, le tout à partir du code QML :

Message {
    body: MessageBody {
        text: "Hello, world!"
    }
}

Propriétés de type liste d’objets

Les propriétés contenant des listes de types dérivés de ` QObject` peuvent également être exposées au QML. À cette fin, il convient toutefois d’utiliser ` QQmlListProperty ` plutôt que ` QList<T>` comme type de propriété. En effet, ` QList ` n’est pas un type dérivé de ` QObject` et ne peut donc pas fournir les caractéristiques de propriété QML nécessaires via le système de méta-objets Qt, telles que les notifications de signaux lorsqu’une liste est modifiée.

Par exemple, la classe MessageBoard ci-dessous possède une propriété messages de type QQmlListProperty qui stocke une liste d’instances de Message:

class MessageBoard : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QQmlListProperty<Message> messages READ messages)
public:
    QQmlListProperty<Message> messages();

private:
    static void append_message(QQmlListProperty<Message> *list, Message *msg);

    QList<Message *> m_messages;
};

La fonction `MessageBoard::messages()` se contente de créer et de renvoyer un ` QQmlListProperty ` à partir de son membre ` QList<T> m_messages `, en transmettant les fonctions de modification de liste appropriées comme l’exige le constructeur ` QQmlListProperty ` :

QQmlListProperty<Message> MessageBoard::messages()
{
    return QQmlListProperty<Message>(this, 0, &MessageBoard::append_message);
}

void MessageBoard::append_message(QQmlListProperty<Message> *list, Message *msg)
{
    MessageBoard *msgBoard = qobject_cast<MessageBoard *>(list->object);
    if (msg)
        msgBoard->m_messages.append(msg);
}

Notez que le type de classe de modèle pour l’ QQmlListProperty — dans ce cas, Message — doit être enregistré auprès du système de types QML.

Propriétés groupées

Toute propriété dont le type possède ses propres sous-propriétés peut être manipulée à l'aide de la syntaxe des propriétés groupées, que ce type soit un type de valeur tel que ` font ` ou un type d'objet. Les propriétés groupées sont utiles pour exposer un ensemble de propriétés liées qui décrivent un ensemble d'attributs pour un type.

Par exemple, supposons que la propriété ` Message::author ` soit de type ` MessageAuthor ` plutôt qu’une simple chaîne de caractères, avec les sous-propriétés ` name ` et ` email` :

class MessageAuthor : public QObject
{
    Q_PROPERTY(QString name READ name WRITE setName)
    Q_PROPERTY(QString email READ email WRITE setEmail)
public:
    ...
};

class Message : public QObject
{
    Q_OBJECT
    Q_PROPERTY(MessageAuthor* author READ author)
public:
    Message(QObject *parent)
        : QObject(parent), m_author(new MessageAuthor(this))
    {
    }
    MessageAuthor *author() const {
        return m_author;
    }
private:
    MessageAuthor *m_author;
};

La propriété author pourrait être définie à l’aide de la syntaxe des propriétés groupées en QML, comme ceci :

Message {
    author.name: "Alexandra"
    author.email: "alexandra@mail.com"
}

author étant une propriété de type objet, les affectations groupées ne créent pas l’objet MessageAuthor. Elles sont attribuées à l’objet renvoyé par la méthode Message::author(). Cela a plusieurs conséquences :

  • Les noms des sous-propriétés sont résolus par rapport au type déclaré de la propriété, ici ` MessageAuthor`, et non par rapport au type de l’objet qui s’y trouve stocké au moment de l’exécution. Différentes instanciations de ` Message ` peuvent produire des objets ` author ` de types différents. La seule chose sur laquelle on peut se fier lors de la création du composant QML est le type déclaré.
  • L’objet doit exister au moment où les affectations groupées sont appliquées. Sinon, le moteur génère une erreur du type Cannot set properties on author as it is null. Créer l’objet dans le constructeur du propriétaire, comme ci-dessus, est le moyen le plus simple de garantir cela, mais un accesseur qui crée l’objet lors du premier accès fonctionne tout aussi bien.
  • La durée de vie de l’objet dépend de ce que prévoit l’implémentation C++. La syntaxe des propriétés groupées ne crée ni ne détruit d’objets.

Déclarer ` author ` en lecture seule, comme dans l’exemple ci-dessus, empêche en outre le code QML de remplacer l’objet ` MessageAuthor `. Si ` author ` disposait d’un accesseur de type ` WRITE `, vous pourriez tout aussi bien lui attribuer un nouvel objet :

Message {
    author: MessageAuthor {
        name: "Alexandra"
        email: "alexandra@mail.com"
    }
}

Vous ne pouvez pas combiner les deux formes pour une même propriété dans une même définition d’objet. Attribuer un objet à ` author ` tout en écrivant ` author.name ` dans le même ` Message ` génère une erreur. Vous pouvez contourner cette restriction en plaçant la propriété groupée dans une portée différente. Cependant, cela donne lieu à des résultats prêtant à confusion, car vous disposez alors de deux objets ` MessageAuthor ` : celui créé par le constructeur et celui assigné explicitement. Seul l’ordre d’évaluation de la liaison détermine à lequel s’applique la propriété groupée.

Cela signifie que la bonne pratique consiste à :

  • Conserver en lecture seule les objets utilisés comme propriétés groupées.
  • Évitez la syntaxe des propriétés groupées si vous manipulez également la propriété de base du groupe lui-même.

Pour une propriété de type valeur telle que font, les affectations groupées nécessitent que la propriété soit accessible en écriture, car le moteur lit la valeur, la modifie, puis la réécrit. La confusion potentielle reste toutefois un problème. Si vous réaffectez l’ensemble de la propriété ` font ` sur place, puis que vous manipulez la propriété ` font.bold ` à un autre endroit, l’ordre d’évaluation des liaisons détermine laquelle est traitée en premier, et quelle valeur ` bold ` en résultera.

Exposition des méthodes (y compris les slots Qt)

Toute méthode d’un type dérivé de ` QObject` est accessible depuis le code QML si elle :

  • Une méthode publique marquée par la macro Q_INVOKABLE()
  • Une méthode qui est un slot Qt public

Par exemple, la classe ` MessageBoard ` ci-dessous dispose d'une méthode ` postMessage() ` marquée par la macro ` Q_INVOKABLE `, ainsi que d'une méthode ` refresh() ` qui est un slot public :

class MessageBoard : public QObject
{
    Q_OBJECT
    QML_ELEMENT

public:
    Q_INVOKABLE bool postMessage(const QString&msg) {
        qDebug() << "Called the C++ method with" << msg;
       return true;
    }

public slots:
    void refresh() {
        qDebug() << "Called the C++ slot";
    }
};

Si une instance de MessageBoard a été définie comme propriété requise pour un fichier MyItem.qml, alors MyItem.qml pourrait appeler les deux méthodes comme indiqué dans les exemples ci-dessous :

C++
int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);

    MessageBoard msgBoard;
    QQuickView view;
    view.setInitialProperties({{"msgBoard", &msgBoard}});
    view.setSource(QUrl::fromLocalFile("MyItem.qml"));
    view.show();

    return app.exec();
}
QML
// MyItem.qml
import QtQuick 2.0

Item {
    required property MessageBoard msgBoard

    width: 100; height: 100

    MouseArea {
        anchors.fill: parent
        onClicked: {
            var result = msgBoard.postMessage("Hello from QML")
            console.log("Result of postMessage():", result)
            msgBoard.refresh();
        }
    }
}

Si une méthode C++ possède un paramètre de type ` QObject* `, la valeur de ce paramètre peut être transmise depuis QML à l’aide d’un objet ` id ` ou d’une valeur JavaScript de type ` var ` faisant référence à cet objet.

QML prend en charge l'appel de fonctions C++ surchargées. S'il existe plusieurs fonctions C++ portant le même nom mais avec des arguments différents, la fonction appropriée sera appelée en fonction du nombre et des types d'arguments fournis.

Les valeurs renvoyées par les méthodes C++ sont converties en valeurs JavaScript lorsqu’elles sont utilisées dans des expressions JavaScript au sein de QML.

Méthodes C++ et l'objet « this »

Vous pouvez souhaiter récupérer une méthode C++ à partir d'un objet et l'appeler sur un autre objet. Prenons l'exemple suivant, dans un module QML appelé « Example » :

C++
class Invokable : public QObject
{
    Q_OBJECT
    QML_ELEMENT
public:
    Invokable(QObject *parent = nullptr) : QObject(parent) {}

    Q_INVOKABLE void invoke() { qDebug() << "invoked on " << objectName(); }
};
QML
import QtQml
import Example

Invokable {
    objectName: "parent"
    property Invokable child: Invokable {}
    Component.onCompleted: child.invoke.call(this)
}

Si vous chargez le code QML à partir d'un fichier main.cpp approprié, il devrait afficher « invoked on parent ». Cependant, en raison d'un bug de longue date, ce n'est pas le cas. Historiquement, l'objet « this » des méthodes basées sur le C++ est indissociablement lié à la méthode. Modifier ce comportement pour le code existant entraînerait des erreurs subtiles, car l'objet « this » est implicite à de nombreux endroits. Depuis Qt 6.5, vous pouvez activer explicitement le comportement correct et permettre aux méthodes C++ d'accepter un objet « this ». Pour ce faire, ajoutez le pragma suivant à vos documents Qml :

pragma NativeMethodBehavior: AcceptThisObject

Une fois cette ligne ajoutée, l'exemple ci-dessus fonctionnera comme prévu.

Remplacer la méthode toString()

Si vous fournissez une méthode Q_INVOKABLE appelée toString (sans argument), cette méthode sera utilisée pour convertir l'objet en chaîne de caractères à la place de l'implémentation native de toString en JavaScript.

Exposition des signaux

Tout signal public d'un type dérivé de ` QObject` est accessible depuis le code QML.

Le moteur QML crée automatiquement un gestionnaire de signal pour tout signal de type dérivé de ` QObject` utilisé depuis le QML. Les gestionnaires de signal sont toujours nommés `on<Signal>`, où ` <Signal> ` correspond au nom du signal, dont la première lettre est en majuscule. Tous les paramètres transmis par le signal sont disponibles dans le gestionnaire de signal via les noms des paramètres.

Par exemple, supposons que la classe ` MessageBoard ` dispose d’un signal ` newMessagePosted() ` avec un seul paramètre, ` subject` :

class MessageBoard : public QObject
{
    Q_OBJECT
public:
   // ...
signals:
   void newMessagePosted(const QString &subject);
};

Si le type ` MessageBoard ` était enregistré dans le système de types QML, alors un objet ` MessageBoard ` déclaré en QML pourrait recevoir le signal ` newMessagePosted() ` à l’aide d’un gestionnaire de signal nommé ` onNewMessagePosted`, et examiner la valeur du paramètre ` subject ` :

MessageBoard {
    onNewMessagePosted: (subject)=> console.log("New message received:", subject)
}

Comme pour les valeurs de propriété et les paramètres de méthode, un paramètre de signal doit avoir un type pris en charge par le moteur QML ; voir Conversion des types de données entre QML et C++. (L’utilisation d’un type non enregistré ne générera pas d’erreur, mais la valeur du paramètre ne sera pas accessible depuis le gestionnaire.)

Les classes peuvent comporter plusieurs signaux portant le même nom, mais seul le dernier signal est accessible en tant que signal QML. Notez que les signaux portant le même nom mais ayant des paramètres différents ne peuvent pas être distingués les uns des autres.

Voir également « QML Type Registration Macros » et « Définition de types QML à partir de C++ ».

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