A practical case : exposing Qt C++ Models to Qml

When it comes to manipulating data within QtQuick, exposing a Qt C++ model is often a good path to take. It allows you to easily separate the logical part of your application from the UI. In addition your models can interact with a SQL database and be processed through some resources heavy filters while still reflecting the changes smoothly onto the UI. Shortly, it is a way of combining the best of both world, the power of C++ with the ease of presentation of QtQuick.

In Tepee3D for instance, we use several models whose representations can be seen onscreen. The UI is composed of several rooms that can each have a variable number of widgets. From a data point of view, we have a model composed of room elements. Each of these room elements then has a model containing widget items. Next, using a Repeater or ListView element, we can dynamically create the visual representation associated to the rooms and the widgets contained in the models.

Tepee3D Models IllustrationThis illustration might help you understand what I’m trying to explain (hoping it doesn’t induce an epilepsy attack as well).

When I first read about C++ models exposed in the Qml’s context of an application, it was on Christophe Dumez’s blog :

http://cdumez.blogspot.ca/2010/11/how-to-use-c-list-model-in-qml.html

I strongly suggest taking a look at his articles as they are really worth a read prior to pursuing this post. You should especially have a look at the ListModel and ListItem classes.

The ListModel classes provided most of the features we needed so we used it and added the ones we found missing.


namespace Models
{

class ListModel : public QAbstractListModel
{
 Q_OBJECT
 Q_PROPERTY(int count READ rowCount
            NOTIFY countChanged)
public:
 explicit ListModel(ListItem *prototype,
                    QObject *parent = 0);
 ~ListModel();

 // REIMPLEMENTED METHODS
 int rowCount(const QModelIndex &parent = QModelIndex())
 const;
 QVariant data(const QModelIndex &index, int role) const;
 QHash<int, QByteArray> roleNames() const;

 void appendRow(ListItem *item);
 void appendRows(QList<ListItem *> &items);
 void insertRow(int row, ListItem *item);
 ListItem* takeRow(int row = -2,
               const QModelIndex &index = QModelIndex());
 QList<ListItem *> takeRows(int row = -2,
    int count = -1,
    const QModelIndex &index = QModelIndex());
 bool removeRow(int row,
                const QModelIndex &index = QModelIndex());
 bool removeRows(int row, int count,
                const QModelIndex &index = QModelIndex());

 ListItem* find(int itemId) const;
 int getRowFromItem(ListItem *item) const;
 QModelIndex indexFromItem(ListItem *item) const;
 QList<ListItem *> toList() const;

 Q_INVOKABLE QVariant get(int index);
 Q_INVOKABLE int rowIndexFromId(int id);
 Q_INVOKABLE void clear();

protected:
 ListItem *prototype;
 QList<ListItem *> items;

private slots :
 void updateItem();

signals :
 void countChanged(int);

};

}


namespace Models
{

class ListItem : public QObject
{
 Q_OBJECT
public :
 ListItem(QObject *parent = 0) : QObject(parent) {}
 virtual ~ListItem() {}
 virtual int id() const = 0;
 virtual QVariant data(int role) const = 0;
 virtual QHash<int, QByteArray> roleNames() const = 0;
 virtual void triggerItemUpdate() {emit dataChanged();}
signals:
 void dataChanged();
};

}

However when you need to obtain a submodel from a ListItem, this gets trickier. Fortunately we found a generic, clean and simple way of doing so. Christophe Dumez’s article shows an example on his article :

http://cdumez.blogspot.ca/2010/12/expose-nested-c-models-to-qml.html

We thought we could improve on his example, actually we found it after but the idea is there. So we created two classes, one inheriting from ListModel the other from ListItem.


namespace Models
{

class SubListedListModel : public Models::ListModel
{
 Q_OBJECT

public:
 explicit SubListedListModel(SubListedListItem
                      *prototype, QObject *parent = 0);

 Q_INVOKABLE QObject* subModelFromId(int id);
};

}


namespace Models
{
class ListModel;

class SubListedListItem : public Models::ListItem
{
 Q_OBJECT
public :
 SubListedListItem(QObject *parent = 0) :
 Models::ListItem(parent) {}
 virtual ~SubListedListItem() {}
 virtual Models::ListModel* submodel() const = 0;
};
}

As you can see, those two classes are small but they provide a simple and effective way of having nested models in Qml. If you want a model that can contain nested models, you need to instantiate a SubListedListModel and provide a derived class of SubListedListItem that implements the submodel method and returns the nested model.

The beauty of it is that you can recursively have n depth nested models this way.  You only have to make some reinterpret_cast<Models::SubListedListItem*>(ListItem *) of your ListItem elements to be able to call the submodel method.

From a Qml point of view, when you wish to retrieve your submodel from your Qml/Js code here is how you proceed assuming you have exposed your model as roomModel to the Qml Context:


ListView
 {
   model : roomModel
   delegate : roomDelegate
 }

 Component
 {
   id : roomDelegate
   RoomLoader // Custom type but could be an Item
   {
     id : room_loader
     roomId : model.roomId
     roomScale : model.roomScale
     roomPosition : model.roomPosition
     // Retrieve the model associated to the
     // ListItem of id roomId in the roomModel model
     subModel : roomModel.subModelFromId(model.roomId)
     source : "Room.qml"
   }
 }

All the code provided here can be downloaded at :
http://gitorious.org/tepee3d

If you’re only interested by the models, please use this link instead :

https://gitorious.org/tepee3d/tepee3d/trees/master/Tepee3DEngine/DeveloperAPIFiles/Models
You can reuse it freely as you wish as long as you keep it opensource.

Advertisements

19 thoughts on “A practical case : exposing Qt C++ Models to Qml

  1. And another question: How can I use your provided ListModel class in an existing QML project? Is it compatible with QtQuick 1.1?

    • Hey,

      CDumez blog used to be the reference and my article was just enhancing his list model a bit. Unfortunately I don’t have a copy of his article. However, I just pushed a small example on github (https://github.com/lemirep/simple_qml_list_model_example) showing how to use the list model class. I hope you’ll be able to figure out how list model work easily. You must disable shadow build in order to run the project though.
      The project is for QtQuick 2 but the principle is the same for QtQuick 1.1.

      Tell me how you go.

      • Oh cool! Thank you for the snippet. I’m only wondering about how to access and change individual rows/roles – because my first attempt before using your ListModel was to use a QList of my own ModelItems and there I had to declare the Q_PROPERTYs with e.g. Q_PROPERTY(int elementID READ elementID WRITE setElementID NOTIFY elementIDChanged)

        Why is this not necessary anymore in your example?

        In my first attempt I was also able to change values of the roles e.g. with myModel[THEID].ROLENAME = VALUE

  2. Hey Florian,

    I’ve updated the snippet and you should now be able to update your model fields the way you wish to : model.RoleName = newValue.
    In the previous version, the ListModel was a read only model, I have updated it so that you can also edit/update values (look at the setData method in ListItem.h and ListModel.cpp).

    The use case of updating a model field directly is something I have not dealt with in this manner before.
    The way I would normally update my model was by calling a C++ Q_INVOKABLE method from QML (of a QObject class instance exposed to the QML context) and passing it my item index and the value to update ex : modelManager.updateBrandForCarModel(index, “New Value”).
    I admit that this is a bit overkill if you just want to edit a field but it is useful when updating a field that also updates other values in related classes or perform SQL queries…

    To answer your question as to why Q_PROPERTY are not needed with the ListModel class, I guess, it is because the QAbstractItemModel from which it inherits must internally create Q_PROPERTY by looking up at the roleNames and data methods of a ListItem instance.

    You might want to look at http://qt-project.org/doc/qt-5.0/qtcore/qabstractitemmodel.html for a deeper explaination.

    • The modelManager.updateBrandForCarModel(index, “New Value”) is also a good idea! I already used the ListModel::setProperty(int index, string rolename, variant value) – If I want to add this function to your ListModelClass I also have to make it Q_INVOKABLE? I will try your suggested aproaches over the next few days 🙂 Thank you very much for you help – I really appreciate that!

  3. If I understand what you are trying to do :
    1) Let’s say your ListModel object was exposed to qml under the my_model name.
    2) You want to change a property my_property of a row identified by row_index by calling my_model.setProperty(row_index, my_property, new_value).
    3) setProperty(int index, string rolename, variant value) is a method you have added in the ListModel class.
    If that is what you want to do, then yes you’ll need to add Q_INVOKABLE in front of your method prototype in ListModel.h so that it can be called from Qml.

    That is a nice solution but I would rather use the setData method, I was talking about in my previous reply.
    I don’t know if my explanation was clear enough, so I’ll try to explain it differently just in case.
    The ListModel class inherits from QAbstractListModel.
    If you want to be able to edit rows of a QAbstractListModel subclass (ListModel in that case), you simply have to overload the method bool QAbstractListModel::setData(const QModelIndex &index, const QVariant &value, int role).
    What I did was that I added this method in the ListModel class and ListModel::setData basically calls the setData method of the ListItem object you used as a prototype for your ListModel instance at the given index.
    The advantage of doing so is that you can easily update a model’s properties from qml by doing model.my_property = my_value directly and there is no need to add Q_INVOKABLE.
    The CarModel example was updated to show this behavior, when you click on a car model row, the price of that car is increased.

  4. Oh ok, yes of course. Sorry – I explained myself also not clear enough. I had some problems with accessing the propertys with myList[rownumber].rolename = value for setting values in a normal QML ListModel – therefore I used the setProperty method to work around the problems 🙂 But you are right: I can also use the setData method because it is already there.

    But now another problem raises: I’m using Qt 4.8 and I ported your example back to this version. Unfortunately it doesn’t work anymore 😦 I can access the model via myCarModel.get(someid).rolename but not in the delegate you shipped your example with. There the Application throws “Unable to assign [undefined] to QString..” all the time. I can’t get my head around this issue – I can send the Qt 4.7 compatible version via Mail if you like!jo

  5. Did you already try to expose also the Item Type by qmlRegisterType? I try to expose the Item class so that I can use it in QML e.g. by using:

    ListModel {
    Car {…}
    Car {…}
    }

    or another use case would be lets say “myModel” is the exposed model than I would like to create a

    Car { id: car1 …}

    and add it by calling myModel.appendRow(car1) to the Model…

    That would be more maintainable and readable than writing big constructors in C++

    But unfortunately I can’t get it to work – I made the appendRow method Q_INVOKABLE and tried to use it in QML by calling it. But than I get the error “Unknown method parameter type: ListItem*” I think QML can’t use pointer but what would then be the way to go to achieve this?

    • To answer the first question, yes you can register a class as a qml type but it must inherits from QObject and you can set its fields only by having declared Q_PROPERTY attributes (so unfortunately you have to add Q_PROPERTY attributes for each fields of the CarModel).

      Concerning the second question, you have done most of the work. The only issue you have is that when sending Car from QML, is it sent as a QObject * and not a ListItem * hence the error you have. To change that the easiest way is to add a new method in the ListModel class that takes a QObject* and casts it to a ListItem*:

      void Models::ListModel::appendRowFromQml(QObject *item)
      {
      // QML can only send QObject *instances so we have to check if the item is a ListItem*
      ListItem *listItem = NULL;
      if ((listItem = reinterpret_cast(item)) != NULL)
      this->appendRow(listItem);
      }
      Note that a reinterpret_cast can be hazardous as it might cast object to ListItem when they might not be really ListItem instances.
      I have updated my sample and implemented thoses features. If you want to define models entirely from QML rather than C++, you might be interested in using javascript and the ListModel QtQuick type.

      • Wow thanks! Great 🙂 That works for me.

        The problem with the suggested method (using javascript and the ListModel QtQuick type) is: I want to export the whole model to xml at some point – so in this case the C++ Model is necessary because I can’t access the ListModel from C++

  6. Thanks for the nice classes you made available (which helped me for sure), but just wanted to let you know, that SubListedListModel and SubListedListItem are actually not needed at all (plus the QML code looks cleaner), as you can always return QVariant::fromValue(Models::ListModel*), and use that value directly in QML code as a model (Q_OBJECT pointers transfer as they should). This way you can also have more than one submodel as a role.

    • This is totally true and should be the preferred way of using nested models as this also takes away QML vs C++ ownership issues that may otherwise arise when returning QObject pointers from Q_INVOKABLE methods. You should just need to have a Q_DECLARE_METATYPE(Models::ListModel*) at the end of the ListModel header to make the conversion to QVariant work. This article is a bit dated and might require a refresh on the the subject of nested models. Thanks for pointing that out.

      • I just saw your article but now i’m wondering how to do it with Qt5x, if i understand it correctly with Qt5 we only need to subclass QAbstractListModel, no need to create the ListItem object right? ANd setRoleNames is now deprecated. So how to retrieve/set the nested model?

      • Right no need to create ListItem, you only need to subclass QAbstractListModel with your own implementation. setRoleNames is deprecated but could still be used. That being said, by overriding QAbstractListModel::roleNames() in your subclass you are doing the same thing as calling setRoleNames

  7. Hi lemire_p,

    I’m creating a menu list model to be used in qml which has a sub-menu of the same structure as the menu list model

    Here’s the declaration and definition
    ************************** DELARATION *****************************
    class MenuItem;

    class MenuModel : public QAbstractListModel
    {
    Q_OBJECT
    public:
    enum MenuRoles {
    NameRole = Qt::UserRole + 1,
    SubMenuRole
    };

    MenuModel(QObject *parent = 0);
    void addMenu(MenuItem *menuitem);
    int rowCount(const QModelIndex & parent = QModelIndex()) const;
    QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;

    protected:
    QHash roleNames() const;

    private:
    QList m_menuItems;
    };

    class MenuItem
    {
    public:
    explicit MenuItem();
    ~MenuItem();

    void setName(QString name);
    QString menuName() const;

    void addSubMenu(MenuItem *subMenuItem);
    MenuModel *subMenu();

    private:
    QString m_strName;
    MenuModel m_subMenuModel;
    };
    **************************************************************************

    ****************************** DEFINITION ****************************
    MenuModel::MenuModel(QObject *parent)
    : QAbstractListModel(parent){}

    void MenuModel::addMenu(MenuItem *menuitem)
    {
    beginInsertRows(QModelIndex(), rowCount(), rowCount());
    m_menuItems << menuitem;
    endInsertRows();
    }

    int MenuModel::rowCount(const QModelIndex & parent) const
    {
    Q_UNUSED(parent);
    return m_menuItems.count();
    }

    QVariant MenuModel::data(const QModelIndex & index, int role) const
    {
    if (index.row() = m_menuItems.count())
    return QVariant();

    MenuItem *menu = m_menuItems[index.row()];
    switch (role) {
    case NameRole:
    return menu->menuName();
    /****ERROR*****/
    // case SubMenuRole:
    // return menu->subMenu();
    default:
    return QVariant();
    break;
    }
    return QVariant();
    }

    QHash MenuModel::roleNames() const
    {
    QHash roles;
    roles[NameRole] = “name”;
    roles[SubMenuRole] = “submenu”;
    return roles;
    }

    MenuItem::MenuItem(){}
    MenuItem::~MenuItem(){}
    void MenuItem::setName(QString name)
    {
    m_strName = name;
    }
    QString MenuItem::menuName() const
    {
    return m_strName;
    }

    void MenuItem::addSubMenu(MenuItem *subMenuItem)
    {
    m_subMenuModel.addMenu(subMenuItem);
    }
    MenuModel* MenuItem::subMenu()
    {
    return &m_subMenuModel;
    }
    **************************************************************************
    I get an error indicated /****ERROR*****/ in the code saying “error: C2248: ‘QVariant::QVariant’ : cannot access private member declared in class ‘QVariant'”

    How can i fix this? and how can i return each item using index?

    Kindly suggest

    Regards,
    val

    • I think you are missing a Q_DECLARE_METATYPE(MenuModel) or you should make MenuModel::subMenu return a QObject* to fix the compile error. If you want to return an item given an index I guess you could add a Q_INVOKABLE QObject *get(QModelIndex index) method to your MenuModel class. But then you will need to have Q_INVOKABLE/slots or Q_PROPERTIES on your MenuItem class to be able to access things

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s