Skip to content

QAbstractItemModels in QML views

April 22, 2010

UPDATE (2013-10-04):

The article below was written for Qt 4.7 tech preview. Since Qt 5.0 things have changed quite a bit. I’ve updated the example code and pushed it to https://github.com/jdahlbom/QtQmlListModel . The article still serves as a useful example for the codebase but I’ve tried to remove any redundant code that I wrote in the original example.

The wonderful people at Trolltech published a tech preview release of Qt 4.7 and its Declarative module in March. Declarative UI, QML and Qt Quick are all synonyms for the new Qt way of prototyping and developing the UI faster, and with less hassle of recompiling.

While the initial documentation for QML will get you far enough to get you excited about it, there are a enough of missing key pieces to really slow you down once you start working on a proper application. My first prototype QML application required access to a model, which is helpfully documented in official QML documentation:

The model provides a set of data that is used to create the items for the view. For large or dynamic datasets the model is usually provided by a C++ model object. The C++ model object must be a QAbstractItemModel subclass or a simple list.

Which gives you the pointer towards QAbstractItemModel, but no help whatsoever towards actually getting it working within QML. After a few weeks of sporadic study and experimentation, I had it finally figured out. The following example is a simple demo about accessing a QAbstractListModel data from the QML script.QML ListView as produced by this demo application.

The model class inheriting from QAbstractListModel is the trickiest part. The header does not look special in any way – just another list model with two custom ItemDataRoles.

SimpleListModel.h:
class DataObject;
class SimpleListModel : public QAbstractListModel {
    Q_OBJECT
public:
    SimpleListModel(QObject *parent=0);
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
    int rowCount(const QModelIndex &parent = QModelIndex()) const;

private:
    Q_DISABLE_COPY(SimpleListModel);
    QList<DataObject*> m_items;
    static const int FirstNameRole;
    static const int LastNameRole;
};

The implementation of the SimpleListModel deserves a more thorough discussion:

SimpleListModel.cpp:
#include "DataObject.h"

// You can define custom data roles starting with Qt::UserRole
const int SimpleListModel::FirstNameRole = Qt::UserRole + 1;
const int SimpleListModel::LastNameRole = Qt::UserRole + 2;

SimpleListModel::SimpleListModel(QObject *parent) :
        QAbstractListModel(parent) {
    // Create dummy data for the list
    DataObject *first = new DataObject(QString("Arthur"), QString("Dent"));
    DataObject *second = new DataObject(QString("Ford"), QString("Prefect"));
    DataObject *third = new DataObject(QString("Zaphod"), QString("Beeblebrox"));
    m_items.append(first);
    m_items.append(second);
    m_items.append(third);

The QML script accesses model data via named roles. While C++ side works with ItemDataRole enums, QML needs to have separately named roles that map to these enums. I am assuming the QML view gets the available role names via QAbstractItemModel::roleNames().

  QHash roles = roleNames();
  roles.insert(FirstNameRole, QByteArray("firstName"));
  roles.insert(LastNameRole, QByteArray("lastName"));
  setRoleNames(roles);
}

int SimpleListModel::rowCount(const QModelIndex &) const {
return m_items.size();
}

The QAbstractItemModel data is accessed via data()-function. For grid or tree models, the QModelIndex might hold more interest to us, but for a simple list like ours we are only interested in the row index, and the requested role. The roles each map to different field of the DataObject:

QVariant SimpleListModel::data(const QModelIndex &index,
                                            int role) const {
    if (!index.isValid())
        return QVariant(); // Return Null variant if index is invalid
    if (index.row() > (m_items.size()-1) )
        return QVariant();

    DataObject *dobj = m_items.at(index.row());
    switch (role) {
    case Qt::DisplayRole: // The default display role now displays the first name as well
    case FirstNameRole:
        return QVariant::fromValue(dobj->first);
    case LastNameRole:
        return QVariant::fromValue(dobj->last);
    default:
        return QVariant();
    }
}

The object displayed in one list cell will use the data stored internally in a DataObject, although the SimpleListModel never reveals the underlying objects.

DataObject.h:
class DataObject {  // my custom container class
public:
  DataObject(const QString &firstName,
             const QString &lastName):
     first(firstName),
     last(lastName) {}
  QString first;
  QString last;
};

In order for the QML to access the model, it needs to be registered for it. I used the QDeclarativeView prototyping class to display the QML elements.

qmlwindow.cpp
#include <QDeclarativeView>
#include <QDeclarativeEngine>
#include <QDeclarativeContext>

#include "SimpleListModel.h"

// A simple main window widget pre-generated by qt creator
QmlWindow::QmlWindow(QWidget *parent)
    : QMainWindow(parent)
{
    model = new SimpleListModel(this);

    view = new QDeclarativeView(this);
    view->engine()->rootContext()->setContextProperty("myModel",
                                                      model);
    view->setSource(QUrl("myuiscript.qml"));
}

qmlwindow.h:
class QmlWindow : public QMainWindow
{
    Q_OBJECT

public:
    QmlWindow(QWidget *parent = 0);
    ~QmlWindow();
private:
    SimpleListModel *model;
    QDeclarativeView *view;
};

The last piece missing from this demo is the actual QML script to display the data:

myuiscript.qml:
import Qt 4.7

Rectangle {
    id: bgRect
    width: 200
    height: 200
    color: "black"
    Component {
        id: myDelegate
        Item {
            width: 200
            height: 40
            Rectangle {
                anchors.fill: parent
                anchors.margins: 2
                radius: 5
                color: "lightsteelblue"
                Row {
                    anchors.verticalCenter: parent.verticalCenter
                    Text {
                        text: model.lastName
                        color: "black"
                        font.bold: true
                    }
                    Text {
                        text: model.firstName
                        color: "black"
                    }
                }
            }
        }
    }
    ListView {
        id: myListView
        anchors.fill: parent
        delegate: myDelegate
        model: myModel
    }
}

The three points that are relevant to discussion at hand: ListView.model property defines the source of all data. We pass the name of the contextProperty we set for the SimpleListModel. ListView.delegate property determines the component used for displaying the data. Within the delegate component, you can access the data of that cell through “model” variable. The properties accessible to this model are determined by the role names of the QAbstractItemModel used.

That concludes the demo application. Just add a main method and the missing includes I stripped off, and you should be good to go.

[EDIT: Fixed the SimpleListModel to append to role names instead of overwriting them]

Advertisements

From → QML, Qt

12 Comments
  1. Turns out a recent Qt snapshot documentation now mentions the usage of ItemDataRoles: http://doc.trolltech.com/4.7-snapshot/qdeclarativemodels.html

  2. steveire permalink

    > I am assuming the QML view gets the available role names via QAbstractItemModel::roleNames().

    Yep, and you should too.

    Instead of

    QHash roles;

    setRoleNames(roles);

    do

    QHash roles = roleNames();

    setRoleNames(roles);

    so that you don’t discard the roles defined by Qt. Then you can just use model.display in the delegate to ask the model for the Qt::DisplayRole for the index.

  3. Thank you for the tip!

    I edited the examples to append the role names instead of overwriting them.

  4. huluyige permalink

    hi, for your code, I don’t get DataObject.cpp. thanks in advance to send it to me. I am really interested in your code.

    thanks

    • The DataObject class is purely a data container, for which the variable data is initialized in the constructor.
      It is fully defined in the header for sake of brevity.
      [EDIT: Fixed “container” -> “constructor”]

  5. Raghavendra permalink

    Thank you very much for this post….i was scouting for this for 3 days and i didnt have clarity about the implementation….this really gave me a clarity….

    • Same here mate! high five! I was searching the web since monday (we have friday now) but finally i managed to solve my problem on my own, but still this site is AWSOM. keep up the good work!
      This definitely lands on my site and in thumbnails 😀

  6. Arur permalink

    what i need for work, i get compile the project, but i get error with qmlwindow, i need create archive.ui or what is the form

    • Arur, I do not completely understand your question.
      This post does not rely on .ui files – it rather loads the qml files directly.
      Although I have to admit that Qt/QML memories are getting a bit fuzzy as I have not
      touched any of it in years.

      • Artur permalink

        don’t worry, finally can get answer my question, by myself.

  7. Swap permalink

    I got following error:
    – missing template argument before ‘roles’
    – expected ‘;’ before ‘roles’
    – ‘roles’ was not declared in this scope

    • I rebuilt this sample from the blog post and found out that I get the same error.

      It also turns out that Qt 5.0 has changed some things, and I did minor rework on the
      example code to get it working with the latest version. Have a look at the github repo
      for a complete example: https://github.com/jdahlbom/QtQmlListModel

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: