Published on

Créez facilement des modèles C++ pour vos QML ListView

Authors
  • Name
    Anthony Rabine

Dans cet article nous allons découvrir comment créer facilement un modèle C++ utilisable dans une ListViw QML

La galère du modèle C++

Qt offre plusieurs moyens pour paratager des données entre le monde C++ et QML. Dans la plupart des cas, le bon découpage est de garder la partie "données" (le modèle) en C++ et de gérer tout l'affichage en QML ; une bonne vieille séparation MVC en sommes (MV à minima).

Un des moyens est de sub-classer une des classes C++ QAbtractMachinModel. C'est une véritable plaie, il ne faut rien oublier, déclarer tout un tas de "roles" et d'accesseurs... bref, cela nécessite de créer un nombre impressionnant de lignes de code assez difficile à maintenir à vrai dire.

Pour certains de mes projets, j'en était rendu à injecter directement du JSON entre le c++ et le QML pour mettre à jour un ListModel, bien plus simple à utiliser.

Vive l'open-source

C'était sans compter la communauté qui, sur Github, fournit tout un tas de classes utilitaires permettant de créer facilement un modèle C++ utilisable dans une ListView ce qui probablement répond à 90% des use-cases (n'oubliez pas mon expression favorite de moi : "La vie est une somme de listes").

Il en existe un bon nombre, je vous présente une classe ici. Notre programme d'exemple se base sur un exercice de programmation que j'ai dûr réaliser pour un recrutement.

Cahier des charges de l'exercice : Cahier des charges

Dans le travail à réaliser, il faut créer une interface en QML montrant une liste de capteur dont le statut et une donnée bougent de manière aléatoire pour montrer l'évolution du niveau de réception par exemple.

Voici à quoi cela doit ressembler :

image

Et mon implémentation est disponible ici : Qt Easy model example

Usage

Voici selon moi un bon modèle d'implémentation qui vous donne une architecture flexible et simple. La liste des fichiers du projet est la suivante :

image

Nous avons donc:

  • Une classe "Device" qui modélise notre capteur
  • Une classe "SensorModel" qui sera notre modèle exporté
  • Le main, bien entendu

Le dernier fichier intéressant est une classe utilitaire "qobject_list_model.h" qui va nous faciliter l'écriture du modèle en C++. Le pré-requis est que l'objet de base hérite de QObject ce qui n'est pas une grosse contrainte quand on fait du Qt !

Utilisez les Q_PROPERTY habituels pour exporter vos données dans QML.

c++
struct Device : QObject
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name CONSTANT )
    Q_PROPERTY(Type type READ type CONSTANT )
    Q_PROPERTY(State state READ state NOTIFY stateChanged )
    Q_PROPERTY(int strength READ strength NOTIFY stateChanged )

    ...

Une fois votre objet créé, utiliez la macro suivante :

c++
// Imagine a class named "Device"
DECLARE_Q_OBJECT_LIST_MODEL( Device )

Cela va vous créer une classe nommée OLM_votre_classe soit ici OLM_Device. Et c'est tout. Il suffit d'instancier cette classe et de l'exporter pour l'utiliser en QML, dans notre cas je le fais ici :

c++
class SensorModel : public QObject
{
    Q_OBJECT

    Q_PROPERTY(OLM_Device* devices READ devices CONSTANT)

public:
    SensorModel(QObject *parent = nullptr);

    // Modèle des devices
    OLM_Device* devices() { return &m_devices; }
    OLM_Device  m_devices;
};

Dans le main.cpp, exportez le modèle et déclarer le type "Device" pour pouvoir utiliser les énumérations facilement en QML.

c++
    SensorModel sensor_model(&app);
    QQmlApplicationEngine engine;
    qmlRegisterUncreatableType<Device>( "App", 1, 0, "Device", "Device is managed from C++" );

    QQmlContext * context = engine.rootContext();
    context->setContextProperty("sensor_model", &sensor_model);

L'usage est simple: dans une ListView, utilisez votre modèle C++ :

javascript
ListView {
    id: idDevicesListView
    anchors.fill: parent
    anchors.margins: 10
    clip: true
    ScrollBar.vertical: ScrollBar {}
    model: sensor_model.devices
    spacing: 4
    delegate: DeviceDelegate {
        width: idDevicesListView.width
    }
}

Dans mon cas ici je crée un composant QML délégué pour séparer l'affichage d'un seul objet dans un fichier séparé pour faciliter la maintenance.

Et voilà le résultat :

image