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

traducteur : Thibaut Cuvelier

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


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.