Utiliser un modèle de liste C++ en QML

Date de publication : 04/02/2012. Date de mise à jour : 16/02/2012.

Par Christophe Dumez Site personnel Blog

 traducteur : Thibaut Cuvelier Site personnel Blog Profil Google+

 

La plupart des exemples QML actuels sont exclusivement écrits en QML et JavaScript, probablement pour les garder simples. Cependant, dans une application réelle, pour des raisons de performances, il est recommandé d'écrire autant de code que possible en C++ et de n'utiliser QML que pour l'interface utilisateur. Au final, il est important de savoir comment utiliser un modèle C++ en QML.
info QML ne fournit de support direct que pour les modèles de liste pour le moment (comme QAbstractListModel, à cause des vues actuellement supportées (ListView, GridView...).
Le but de cet article est de montrer une manière facile d'utiliser un modèle de liste en C++ en QML au lieu d'un modèle QML comme ListModel.

Viadeo Twitter Google Bookmarks ! Facebook Digg del.icio.us MySpace Yahoo MyWeb Blinklist Netvouz Reddit Simpy StumbleUpon Bookmarks Windows Live Favorites      



I. L'article original
II. Première étape : écrire un modèle de liste en C++ qui dérive de QAbstractListModel
III. Deuxième étape : créer une classe d'item pour le modèle (ListItem)
IV. Exposer le modèle C++ à QML
V. Créer une vue QML qui utilise le modèle C++
VI. Remerciements


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écharger
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écharger
/*
 * Author: Christophe Dumez <dchris@gmail.com>
 * License: Public domain (No attribution required)
 * Website: http://cdumez.blogspot.com/
 * Version: 1.0
 */
 
#include "listmodel.h"
 
ListModel::ListModel(ListItem* prototype, QObject *parent) :
    QAbstractListModel(parent), m_prototype(prototype)
{
  setRoleNames(m_prototype->roleNames());
}
 
int ListModel::rowCount(const QModelIndex &parent) const
{
  Q_UNUSED(parent);
  return m_list.size();
}
 
QVariant ListModel::data(const QModelIndex &index, int role) const
{
  if(index.row() < 0 || index.row() >= m_list.size())
    return QVariant();
  return m_list.at(index.row())->data(role);
}
 
ListModel::~ListModel() {
  delete m_prototype;
  clear();
}
 
void ListModel::appendRow(ListItem *item)
{
  appendRows(QList<ListItem*>() << item);
}
 
void ListModel::appendRows(const QList<ListItem *> &items)
{
  beginInsertRows(QModelIndex(), rowCount(), rowCount()+items.size()-1);
  foreach(ListItem *item, items) {
    connect(item, SIGNAL(dataChanged()), SLOT(handleItemChange()));
    m_list.append(item);
  }
  endInsertRows();
}
 
void ListModel::insertRow(int row, ListItem *item)
{
  beginInsertRows(QModelIndex(), row, row);
  connect(item, SIGNAL(dataChanged()), SLOT(handleItemChange()));
  m_list.insert(row, item);
  endInsertRows();
}
 
void ListModel::handleItemChange()
{
  ListItem* item = static_cast<ListItem*>(sender());
  QModelIndex index = indexFromItem(item);
  if(index.isValid())
    emit dataChanged(index, index);
}
 
ListItem * ListModel::find(const QString &id) const
{
  foreach(ListItem* item, m_list) {
    if(item->id() == id) return item;
  }
  return 0;
}
 
QModelIndex ListModel::indexFromItem(const ListItem *item) const
{
  Q_ASSERT(item);
  for(int row=0; row<m_list.size(); ++row) {
    if(m_list.at(row) == item) return index(row);
  }
  return QModelIndex();
}
 
void ListModel::clear()
{
  qDeleteAll(m_list);
  m_list.clear();
}
 
bool ListModel::removeRow(int row, const QModelIndex &parent)
{
  Q_UNUSED(parent);
  if(row < 0 || row >= m_list.size()) return false;
  beginRemoveRows(QModelIndex(), row, row);
  delete m_list.takeAt(row);
  endRemoveRows();
  return true;
}
 
bool ListModel::removeRows(int row, int count, const QModelIndex &parent)
{
  Q_UNUSED(parent);
  if(row < 0 || (row+count) >= m_list.size()) return false;
  beginRemoveRows(QModelIndex(), row, row+count-1);
  for(int i=0; i<count; ++i) {
    delete m_list.takeAt(row);
  }
  endRemoveRows();
  return true;
}
 
ListItem * ListModel::takeRow(int row)
{
  beginRemoveRows(QModelIndex(), row, row);
  ListItem* item = m_list.takeAt(row);
  endRemoveRows();
  return item;
}
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
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
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 :
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
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
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 !



               Version PDF (Miroir)   Version hors-ligne (Miroir)

Valid XHTML 1.0 TransitionalValid CSS!

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. Cette page est déposée.

 
 
 
 
Partenaires

Hébergement Web