Note that QML only provides direct support for list models at the moment (e.g. QAbstractListModel) because of currently supported views (ListView, GridView, ...).
The purpose of this post is to demonstrate an easy way to use a C++ list model in QML instead of a QML model such as ListModel.
Step 1: Write a C++ list model which subclasses QAbstractListModel
As an example, I have written a ListModel class which subclasses QAbstractListModel and provides the most commonly used methods.
It uses a QList container to store the items and define a ListItem abstract class to help define compliant item classes (see Step 2).
Here is what listmodel.h looks like:
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;
};
You can download the files there (public domain):
You can very easily reuse these files in order to create your model.
Step 2: Create a model item class (ListItem)
I will now demonstrate how to subclass the ListItem class in order to create items. Let's consider the example of a fruit which has the following properties : name, size (medium, small, large) and price.
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();
}
}
One important thing to understand is the roleNames() function. It will be used by our ListModel to return its role names (see QAbstractItemModel::roleNames()). This will allow the QML delegate to query the different item properties by using their assigned role name.Also notice that the price can be changed later on using the setPrice() method. For this reason, we made sure that setPrice() emits the dataChanged() signal so that the model can relay it to the view and so that the view gets updated.
Step 3: Exposing the C++ model to QML
One easy way to integrate C++ with QML is to use the QDeclarativeView class. It provides a widget for displaying a Qt Declarative user interface.
You can then use QDeclarativeView::rootContext()::setContextProperty(QString name, QObject* value) to expose your model instance to the QML and assign it a variable name (usable from QML).
See the following example:
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();
}
The createModel() function is used to create a ListModel instance using our previously defined FruitItem as prototype. It also populates the model with a few dummy items before returning it to the caller.
Step 4: Create a QML view that uses the C++ model
Using your C++ model in QML is now as easy as assigning its variable name (that you defined when exposing the model using setContextProperty()) to your view's model property.
The following example uses a QML ListView to display the data of our fruits model:
main.qml
import QtQuick 1.0
Rectangle {
width: 360
height: 360
ListView {
id: fruitsView
anchors.fill: parent
model: fruitsModel
delegate: DummyDelegate{}
}
}
And here is the delegate's code which accesses the model items properties using their assigned role names (here name and size):
DummyDelegate.qml
import QtQuick 1.0
Item {
id: delegate
height: 30
width: delegate.ListView.view.width
Text {
anchors.fill: parent;
text: name +" ("+size+")"
}
}
That's it!
Another easy way using QStandardItemModel:
ReplyDeletehttp://wiki.forum.nokia.com/index.php/Using_QStandardItemModel_in_QML
Hi,
ReplyDeletethanks for this (and all other too) post!
Really useful!
Thanks very much for this very useful post.
ReplyDeleteWhat if I wanted to add a function (say, ChangeSize() on the cpp side such that it changes m_size. What would be needed to call this function from qml on a click of a mouse area filling the text of the list view item?
ReplyDeleteI've tried something like this:
onClicked: fruitsModel.find( name ).ChangeSize();
and have declared both the find() and ChangeSize() functions Q_INVOKABLE. But this doesn't work.
Any ideas?
Hey Chris,
ReplyDeleteThanks for the wonderful explanation and code. I'm new to QML and was struggling to pass across a model through C++. The docs were driving me crazy and time was running on...Thanks for the classes too!
Totally agree with this guy ^
ReplyDeleteI have been trying to get this to work for a few days now and your tutorial really helped me to understand the interfacing between QML and the C++. You are my hero of the week!
Simply amazing, thank you very much!
ReplyDeleteI have problem on removing a record from list if the list only has one item. It cannot be done. please advice.
ReplyDeleteHi Chris, I mentioned your blog entry in a post on my blog, I also provide some addition to your code which might be interesting for other developers. Here is the link: http://bsauts-en.blogspot.com/2011/09/listmodel-in-c-exposed-to-qml.html
ReplyDeleteThanks again for your code. :)
Hi Chris! Thanks for the nice example!
ReplyDeleteI have a question though.. When trying to set the model from the Qml side, performance is really really bad. Do you have any idea why?
Thanks again!
Hi Chris! At the end, it was because of something stupid I was doing on qml side that damaged my performance. Nothing to do with your example. It works perfect! thanks
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteThanks .. i got it working easily !!
ReplyDeleteThere is something fragile that leaves me confused.
ReplyDeleteIf I change the main() function to:
ListModel *mymodel = createModel();
view.rootContext()->setContextProperty("fruitsModel", mymodel);
it stops working.
But the original
view.rootContext()->setContextProperty("fruitsModel", createModel());
does work.
I wonder if something has changed in underlying Qt/QML since the article was written.
Or something subtle in scoping/destruction of roleNames hash is happening...
Is your clear method works dynamicaly ? I mean , if you press a QML button which call the clear function, will the view be clear ? Because you didn't set any signal.. beginReset() ..
ReplyDeleteThank you very much! It helped me alot.
ReplyDeleteHi Chris, thanks for the tips! :)
ReplyDeleteI know it's an old post, but for further reader, I think there is a mistake in ListModel::removeRows(...).
The line : if(row < 0 || (row+count) >= m_list.size()) return false;
should be : if(row < 0 || (row+count) > m_list.size()) return false;
(replace >= by >)
Best regards,
-Gauthier
Awesome article!
ReplyDeleteA tip for the beginners(maybe too basic, but in case you didn't look up the model subclass reference): it's not necessary to shape the derived item to have role name handling. There're some other approaches, such as member function overloading, or even directly accessing the data members.
Thank You Chris!
ReplyDeleteThis example was still very helpful 2 years after you originally wrote it!
I also found Benoît's extensions helpful in understanding how and when to extend the derived ListModel class.
I struggled with adding the ability to write to existing item's by role, and eventually found a solution. Some discussion is in this qt-project forum post:
http://qt-project.org/forums/viewthread/21787/
Thanks again for your great post!
johnea
Can someone give an example. how to add method move() to such C++ model? ListModel has one and I have no idea how to implement it.
ReplyDeleteWith Qt 5.0 it says: setRoleNames: was not declared in this scope.
ReplyDeleteAny ideas to make your code work with Qt 5.0 ?
(I'm a newbie in Qt Programming)
In Qt 5.0 "QAbstractListModel::setRoleNames" was removed. The object's properties are declared with Q_PROPERTY instead, e.g.
Deleteclass FruitItem //: public ListItem <- not necessary anymore
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
....
signals:
void nameChanged();
...
Now, "name" should correctly access the name-Property.
All other declared methods related to roles can be removed, too. Just remove the offending "setRoleNames(m_prototype->...)" line.