ELCa11 - Tuto : faire communiquer C++ et QML¶
Objectifs¶
- Construire une application Qt Quick combinant une interface en QML et une logique métier en C++.
- Maîtriser les quatre mécanismes essentiels de communication QML ↔ C++ :
setContextProperty— exposer un objet C++ à QML ;Q_INVOKABLE— appeler une méthode C++ depuis QML ;Q_PROPERTY— exposer un attribut comme propriété QML ;- signaux Qt — notifier QML d’un changement côté C++.
L’exemple : un compteur affiché à l’écran, que l’on incrémente ou décrémente avec les flèches du clavier (↑ / ↓).
Prérequis¶
- Installer Qt ;
- Tuto QML Component — la création de composants QML doit être familière.
Aparté : signaux et slots Qt¶
Qt met à disposition un mécanisme original de communication entre objets, appelé signaux et slots (signals and slots) :
- un signal est émis par un objet quand un événement se produit (valeur changée, bouton cliqué, etc.) ;
- un slot est une méthode qui s’exécute en réaction à un signal qu’on lui a connecté ;
- la connexion entre les deux se fait avec
QObject::connect(...).
C’est plus souple que les callbacks C++ classiques et permet à des objets de se notifier sans dépendre directement les uns des autres. C’est aussi le mécanisme qui permet à QML de réagir automatiquement quand un attribut C++ change : la classe C++ émet un signal, le moteur QML l’attrape et rafraîchit l’affichage.
Étape 1 — Créer le projet et l’arbre de scène QML (partie statique)¶
- Lancer Qt Creator. Menu
Fichier>Nouveau projet…. - Choisir
Application (Qt)>Application Qt Quick. - Nom :
Compteur(sans année — garder un nom générique). Système de build :CMake. - Valider les écrans suivants par défaut.
Compiler et exécuter (▶ ou Ctrl+R / Cmd+R) pour vérifier que la fenêtre vide s’affiche.
Ouvrir Main.qml et basculer en mode Conception (icône en bas à gauche, ou Mode > Design).
Construire l’interface :
- Fenêtre (
Window) : taille 320 × 240, titreCompteur. - Rectangle principal (
anchors.fill: parent) avec une couleur de fond au choix. - Rectangle centré, taille 100 × 50, couleur contrastante, identifiant
boite. - Élément
Textcentré dans laboite, taille 20 pixels, gras, police Tahoma (ou autre). - Cocher
focus: truesur le rectangle principal (panneau Propriétés > Avancé, en bas).
focus: trueest indispensable pour que la fenêtre reçoive les événements clavier (sinon, les flèches ne déclencheront rien).
Étape 2 — Créer la classe C++ Compteur¶
- Clic droit sur le projet dans la barre latérale >
Ajouter un nouveau…. - Choisir
C++>Classe C++> Choisir…. - Nom de la classe :
Compteur. CocherHériter de QObject(la macroQ_OBJECTsera ajoutée automatiquement). - Valider.
Qt Creator crée compteur.h et compteur.cpp. Hériter de QObject est indispensable pour utiliser les signaux/slots, Q_INVOKABLE et Q_PROPERTY.
Dans compteur.h, ajouter :
#ifndef COMPTEUR_H
#define COMPTEUR_H
#include <QObject>
#include <QString>
class Compteur : public QObject {
Q_OBJECT
Q_PROPERTY(QString cptQML READ readCompteur NOTIFY cptChanged)
public:
explicit Compteur(QObject* parent = nullptr);
Q_INVOKABLE void increment();
Q_INVOKABLE void decrement();
signals:
void cptChanged();
private:
QString readCompteur() const;
int fCompteur;
};
#endif // COMPTEUR_H
Quelques explications :
Q_OBJECT: macro indispensable dans toute classe utilisant signaux/slots/properties Qt.Q_INVOKABLE: marque une méthode comme appelable depuis QML.-
Q_PROPERTY(QString cptQML READ readCompteur NOTIFY cptChanged): déclare une propriété QML nomméecptQML, de typeQString, dont :- la valeur s’obtient en appelant
readCompteur()(READ) ; - les changements sont annoncés par le signal
cptChanged(NOTIFY).
Pas de
WRITE: la propriété est lecture seule depuis QML (on ne peut pas écrirevueObjetCpt.cptQML = "..."). On y reviendra plus bas. - la valeur s’obtient en appelant
Pas de
;à la fin de la ligneQ_PROPERTY— c’est une macro, pas une déclaration.
Implémentation dans compteur.cpp :
#include "compteur.h"
Compteur::Compteur(QObject* parent) : QObject(parent), fCompteur(10) {}
void Compteur::increment() {
++fCompteur;
emit cptChanged();
}
void Compteur::decrement() {
--fCompteur;
emit cptChanged();
}
QString Compteur::readCompteur() const {
return QString::number(fCompteur);
}
emit cptChanged();: envoie le signal à tous les objets qui y sont connectés. QML est connecté automatiquement à ce signal grâce àNOTIFYdans laQ_PROPERTY— il rafraîchira l’affichage tout seul.
Étape 3 — Exposer l’objet C++ à QML (main.cpp)¶
Remplacer le contenu de main.cpp par :
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "compteur.h"
int main(int argc, char* argv[]) {
QGuiApplication app(argc, argv);
Compteur aCompteur; // notre objet C++
QQmlApplicationEngine engine;
QObject::connect(
&engine,
&QQmlApplicationEngine::objectCreationFailed,
&app,
[]() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
// Rend aCompteur accessible côté QML sous le nom "vueObjetCpt"
engine.rootContext()->setContextProperty("vueObjetCpt", &aCompteur);
engine.loadFromModule("Compteur", "Main");
return app.exec();
}
Les points-clés :
Compteur aCompteur;crée l’objet C++.engine.rootContext()->setContextProperty("vueObjetCpt", &aCompteur);rend cet objet visible côté QML, sous le nomvueObjetCpt. Vous y accéderez avec ce nom dans tout le code QML.engine.loadFromModule("Compteur", "Main");chargeMain.qml(le"Compteur"est le nom du module, ici le nom du projet).
Compiler et exécuter — aucun changement visible à ce stade, c’est normal : on a exposé l’objet, mais QML ne l’utilise pas encore.
Étape 4 — Utiliser l’objet C++ depuis QML¶
Ouvrir Main.qml. Modifier l’élément Text pour lire la valeur du compteur, et ajouter la gestion des flèches sur le rectangle principal :
Text {
id: txt
anchors.centerIn: parent
text: vueObjetCpt.cptQML
font.pixelSize: 20
font.bold: true
font.family: "Tahoma"
}
Et sur le rectangle principal (qui a focus: true) :
Keys.onPressed: (event) => {
switch (event.key) {
case Qt.Key_Up:
vueObjetCpt.increment();
break;
case Qt.Key_Down:
vueObjetCpt.decrement();
break;
}
}
Compiler et exécuter :
- la valeur initiale 10 doit s’afficher ;
- ↑ doit incrémenter, ↓ décrémenter ;
- la mise à jour est instantanée grâce au signal
cptChangedconnecté automatiquement à la propriété QML.
Étape 5 — Code final attendu¶
compteur.h et compteur.cpp : voir Étape 2.
main.cpp : voir Étape 3.
Main.qml :
import QtQuick
Window {
width: 320
height: 240
visible: true
title: qsTr("Compteur")
Rectangle {
id: fond
anchors.fill: parent
color: "lightyellow"
focus: true
Keys.onPressed: (event) => {
switch (event.key) {
case Qt.Key_Up:
vueObjetCpt.increment();
break;
case Qt.Key_Down:
vueObjetCpt.decrement();
break;
}
}
Rectangle {
id: boite
anchors.centerIn: parent
width: 100
height: 50
color: "steelblue"
Text {
id: txt
anchors.centerIn: parent
text: vueObjetCpt.cptQML
font.pixelSize: 20
font.bold: true
font.family: "Tahoma"
color: "white"
}
}
}
}
Pour aller plus loin¶
Rendre la propriété modifiable depuis QML (WRITE)¶
Si l’on veut pouvoir écrire la valeur du compteur depuis QML (par ex. via un champ texte), il faut ajouter WRITE à la Q_PROPERTY :
et fournir la méthode correspondante :
void Compteur::writeCompteur(const QString& s) {
int nouvelle = s.toInt();
if (nouvelle != fCompteur) {
fCompteur = nouvelle;
emit cptChanged();
}
}
Bonne pratique : dans une fonction
WRITE, toujours vérifier que la nouvelle valeur diffère de l’ancienne avant d’émettre le signal — sinon, on risque des boucles d’événements et des mises à jour inutiles.
Exercice — borner le compteur¶
Dans sa version actuelle, le compteur peut prendre n’importe quelle valeur entière. Étendre l’application pour qu’il reste dans un intervalle borné, par exemple [CPT_MIN, CPT_MAX] = [0, 20].
Pistes :
- Modifier
increment()etdecrement()pour ne rien faire si la nouvelle valeur sortirait des bornes. - Faut-il avertir l’utilisateur quand on tente de dépasser une borne (ex. changer brièvement la couleur du texte) ?
- Exposer
CPT_MINetCPT_MAXà QML (via deux nouvellesQ_PROPERTYen lecture seule) pour pouvoir les afficher dans l’interface.