I. L'article original

Cet article est une adaptation en langue française de How to use a C++ list model in QML.

II. Première étape : écrire un modèle de liste en C++ qui dérive de QAbstractListModel

Pour l'exemple, on a écrit une classe ListModel qui hérite de QAbstractListModel et fournit les méthodes les plus couramment utilisées. Elle utilise un conteneur QList pour stocker les items et définit une classe abstraite ListItem pour aider à définir des classes d'items qui conviennent.

listmodel.h
TéléchargerSélectionnez
class ListItem: public QObject {
  Q_OBJECT
 
public:
  ListItem(QObject* parent = 0) : QObject(parent) {}
  virtual ~ListItem() {}
  virtual QString id() const = 0;
  virtual QVariant data(int role) const = 0;
  virtual QHash<int, QByteArray> roleNames() const = 0;
 
signals:
  void dataChanged();
};
 
class ListModel : public QAbstractListModel
{
  Q_OBJECT
 
public:
  explicit ListModel(ListItem* prototype, QObject* parent = 0);
  ~ListModel();
  int rowCount(const QModelIndex &parent = QModelIndex()) const;
  QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
  void appendRow(ListItem* item);
  void appendRows(const QList<ListItem*> &items);
  void insertRow(int row, ListItem* item);
  bool removeRow(int row, const QModelIndex &parent = QModelIndex());
  bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
  ListItem* takeRow(int row);
  ListItem* find(const QString &id);
  QModelIndex indexFromItem( const ListItem* item) const;
  void clear();
 
private slots:
  void handleItemChange();
 
private:
  ListItem* m_prototype;
  QList<ListItem*> m_list;
};
listmodel.cpp
TéléchargerCacherSélectionnez

On peut réutiliser facilement ces fichiers pour créer son propre modèle.

III. Deuxième étape : créer une classe d'item pour le modèle (ListItem)

On montre ici comment hériter de la classe ListItem pour créer des items. On considère l'exemple d'un fruit ayant les propriétés de nom, taille (petit, moyen, grand) et prix.

fruititem.h
Sélectionnez
class FruitItem : public ListItem
{
  Q_OBJECT
 
public:
  enum Roles {
    NameRole = Qt::UserRole+1,
    SizeRole,
    PriceRole
  };
 
public:
  FruitItem(QObject *parent = 0): ListItem(parent) {}
  explicit FruitItem(const QString &name, const QString &size, QObject *parent = 0);
  QVariant data(int role) const;
  QHash<int, QByteArray> roleNames() const;
  void setPrice(qreal price);
  inline QString id() const { return m_name; }
  inline QString name() const { return m_name; }
  inline QString size() const { return m_size; }
  inline qreal price() const { return m_price; }
 
private:
  QString m_name;
  QString m_size;
  qreal m_price;
};
fruititem.cpp
Sélectionnez
FruitItem::FruitItem(const QString &name, const QString &size, QObject *parent) :
  ListItem(parent), m_name(name), m_size(size), m_price(-1)
{
}
 
void FruitItem::setPrice(qreal price)
{
  if(m_price != price) {
    m_price = price;
    emit dataChanged();
  }
}
 
QHash<int, QByteArray> FruitItem::roleNames() const
{
  QHash<int, QByteArray> names;
  names[NameRole] = "name";
  names[SizeRole] = "size";
  names[PriceRole] = "price";
  return names;
}
 
QVariant FruitItem::data(int role) const
{
  switch(role) {
  case NameRole:
    return name();
  case SizeRole:
    return size();
  case PriceRole:
    return price();
  default:
    return QVariant();
  }
}

Il est important de comprendre la fonction roleNames(). Elle sera utilisée par le ListModel pour retourner ses noms de rôle (voir QAbstractItemModel::roleNames()). Ainsi, le délégué QML aura accès aux différentes propriétés de l'item en utilisant les noms de rôle assignés.

De même, le prix peut être changé en utilisant la méthode setPrice(). Pour cette raison, on s'assure que setPrice() émet le signal dataChanged() pour que le modèle transmette l'information à la vue, qui sera alors mise à jour.

IV. Exposer le modèle C++ à QML

Une manière facile d'intégrer C++ et QML est d'utiliser la classe QDeclarativeView. Elle fournit un widget pour afficher une interface graphique déclarative. On peut alors utiliser QDeclarativeView::rootContext()::setContextProperty(QString name, QObject* value) pour exposer une instance du modèle à QML et lui assigner un nom de variable utilisable depuis QML.

Voici un exemple :

 
Sélectionnez
ListModel* createModel() {
  ListModel *model = new ListModel(new FruitItem, qApp);
  model->appendRow(new FruitItem("Apple", "medium", model));
  model->appendRow(new FruitItem("PineApple", "big", model));
  model->appendRow(new FruitItem("Grape", "small", model));
  return model;
}
 
int main(int argc, char *argv[])
{
  QApplication app(argc, argv);
 
  QDeclarativeView view;
  view.rootContext()->setContextProperty("fruitsModel",  createModel());
  view.setSource(QUrl::fromLocalFile("qml/QMLCPP/main.qml"));
  view.show();
 
  return app.exec();
}

La fonction createModel() crée une instance de ListModel en utilisant la classe FruitItem, que l'on vient de définir, comme prototype. Elle remplit aussi le modèle avec quelques items avant de le retourner à l'appelant.

V. Créer une vue QML qui utilise le modèle C++

Utiliser le modèle C++ en QML est maintenant aussi facile que lui assigner son nom de variable (défini lors de l'exposition du modèle avec setContextPropery()) à la propriété de modèle de la vue.

L'exemple suivant utilise une vue ListView de QML pour afficher les données du modèle de fruits :

main.qml
Sélectionnez
import QtQuick 1.0
 
Rectangle {
  width: 360
  height: 360
  ListView {
    id: fruitsView
    anchors.fill: parent
    model: fruitsModel
    delegate: DummyDelegate{}
  }
}

Voici le code du délégué qui accède aux propriétés des items du modèle en utilisant les noms de rôle assignés (ici, name et size) :

DummyDelegate.qml
Sélectionnez
import QtQuick 1.0
 
Item {
  id: delegate
  height: 30
  width: delegate.ListView.view.width
 
  Text {
    anchors.fill: parent;
    text: name +" ("+size+")"
  }
}

VI. Remerciements

Merci à Guillaume Belz et Claude Leloup pour leur relecture attentive !