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

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

2 commentaires Donner une note à l'article (5)

Article lu   fois.

Les deux auteurs

Site personnel

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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.

 
Sélectionnez
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.

 
Sélectionnez
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 :

 
Sélectionnez
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
Sélectionnez
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
Sélectionnez
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 !

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

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.