Exposer des modèles C++ imbriqués à QML

| Christophe Dumez | traducteur : Thibaut Cuvelier |

Bien que ce cas d'imbrication puisse sembler rare en pratique, le fait que XML n'ait pas de support direct pour les modèles arborescents rend l'utilisation de modèles C++ imbriqués très utile pour obtenir une structure en arbre.

Un exemple de cas pratique où les modèles imbriqués sont utiles est le stockage de conversations Facebook. Un mur Facebook est constitué de notifications sociales (modèle racine), chacun pouvant avoir des commentaires (modèles internes).

Commentez

- Titre : Exposer des modèles C++ imbriqués à QML

- Auteurs : Christophe Dumez, traducteur : Thibaut Cuvelier

- Parution : 6 février 2012

- Mise à jour : 17 mars 2012

- Licence : Copyright ® 2012 Christophe Dumez. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. L'article original

- II. Côté C++

- III. Côté QML

- VI. Remerciements

I - L'article original

Cet article est une adaptation en langue française de Expose nested C++ models to QML.

II - Côté C++

Le point de départ est la création d'un modèle C++ qui hérite de QAbstractListModel, comme expliqué dans l'article Utiliser un modèle de liste C++ en QML. On appellera ce modèle FacebookWallModel. Dans l'exemple, chaque item de ce modèle (on appelle leur classe SocialUpdateItem) aura comme propriété un autre modèle contenant les commentaires (CommentsModel).

Voici une version raccourcie du code de SocialUpdateItem. Il ne devrait pas y avoir de surprise.

typedef ListModel CommentsModel;

class SocialUpdateItem: public ListItem {
Q_OBJECT

public:
enum Roles {
IdRole = Qt::UserRole+1,
SenderRole,
DescriptionRole,
PostTimeRole,
AvatarRole /* ... */
};

public:
SocialUpdateItem(): ListItem() {}
SocialUpdateItem(QString id, QObject *parent=0): ListItem(parent), m_id(id) {
// FIXME : instancier m_comments
}

inline QString id() const {
return m_id;
}

QVariant data(int role) const {
switch(role) {
case IdRole:
return m_id;
/* D'autres rôles... */
default:
return QVariant();
}
}

QHash<int, QByteArray> roleNames() const {
QHash<int, QByteArray> roles;
roles[IdRole] = "socialUpdateId";
roles[SenderRole] = "sender";
roles[DescriptionRole] = "description";
/* D'autres rôles... */
return roles;
}

CommentsModel* comments() const {
return m_comments;
}

private:
QString m_id;
CommentsModel *m_comments;
};

À présent, voici FacebookWallModel, le modèle qui sera directement exposé à QML.

class FacebookWallModel : public ListModel
{
Q_OBJECT

public:
explicit FacebookWallModel(QObject *parent = 0):
ListModel(new SocialUpdateItem, parent) {
}

Q_INVOKABLE QObject* activityComments(const QString &id) const {
SocialUpdateItem *update = static_cast<SocialUpdateItem*>(find(id));
if(update) {
return update->comments();
}
return 0;
}
};

Comme on peut le voir, le truc, ici, est d'utiliser la macro Q_INVOKABLE pour rendre la fonction activityComments() disponible à QML, comme expliqué dans la documentation. On utilisera cette fonction dans le code QML plus tard pour avoir accès aux commentaires depuis le délégué SocialUpdate. Notez que cette fonction retourne un pointeur sur QObject au lieu d'un pointeur sur ListModel (ou QAbstractListModel). Ceci est requis pour le faire fonctionner, puisque QML ne reconnaît pas d'autres types de données (voir la liste des types supportés).

On expose alors ce modèle à QML :

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

QDeclarativeView view;
FacebookWallModel wall_model(&app);
view.rootContext()->setContextProperty("wallModel",  &wall_model);
view.setSource(QUrl::fromLocalFile("qml/NestedModels/main.qml"));
view.show();

return app.exec();
}

III - Côté QML

En QML, on utilisera une ListView pour afficher les données du FacebookWallModel.

main.qml

Rectangle {
width: 360
height: 360

ListView {
id: wallView
anchors.fill: parent
model: wallModel
delegate: SocialUpdateDelegate{}
}
}

Dans le délégué SocialUpdate, on peut afficher le texte de la notification mais aussi le commentaire. Pour accéder au commentaire de l'activité sociale actuelle, on appelle la fonction C++ activityComments(). Dans l'exemple, on utilise l'identifiant de notification sociale pour récupérer son commentaire ; cependant, on aurait pu utiliser son index dans le modèle (voir ListView.currentIndex).

SocialUpdateDelegate.qml

Rectangle {
id: socialUpdateDelegate
width: socialUpdateDelegate.ListView.view.width
height: updateCol.height
color: "#c0c0c0"

Column {
id: updateCol
width: parent.width

Text {
id: updateText
width: parent.width
text: "<b>"+sender+":</b> "+description
}

Rectangle {
id: commentsBox
color: "white"
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 5
anchors.rightMargin: 5
height: commentsCol.height

Column {
id: commentsCol
width: parent.width

Repeater {
id: comments
width: parent.width
model: wallModel.activityComments(socialUpdateId)
delegate: CommentDelegate{}
}
}
}
}
}

On a utilisé un Repeater au lieu d'une ListView pour afficher les commentaires, car il est plus léger et suffit aux besoins.

VI - Remerciements

Merci à Claude Leloup pour sa relecture attentive !