Saturday, September 3, 2011

ListModel in C++ exposed to QML

Hi there people,

I have been working a lot lately on my master thesis and I'm currently developing the prototype of my system. It's getting bigger and bigger, mixing Qt/C++ and QML/Javascript. So far, it's working like a charm.

One interesting behavior of my system is how my system is handling data. Indeed, I needed QML ListModels for my QML UI but I also needed some persistence... So of course I created a database... but since I need to access the database from the C++ side, I couldn't really use the Offline Storage capability of QML since the database generated in this case is randomly named... This would have been quite time wasting to try to access it... So, instead, I create my database with a database manager class written in C++. This database is a simple SQLite database.

At start up, I create several C++ ListModels which are populated from the database and that I link to the instance of the QMLApplicationViewer, this way I can access their data from my QML ListViews and GridViews. The C++ ListModel class that I use, is taken directly from here.

I owe Chris a big big thank for providing this implementation of a ListModel and for giving an example showing how to use the stuff. So Chris, if you read this blog entry, THANKS A LOT!!! :)

I will not explain how this works, Chris is doing that quite well on his own. Just check the given link. But, here comes the why of this blog entry. The implementation provided by Chris is somehow limited. In fact some interesting functionality which are provided in a normal QML ListModel such as count or get() are missing. I guess he let that task for us, or maybe he just didn't think someone would need it.

In my case, I needed to split one of these ListModel in 3 different sub models... I tried to use the QSortFilterProxyModel class but this didn't work... So I came up with another way to do it.

As you might all know (or might know soon by checking this link), when you create a class which is meant to be exposed to QML, you can provide functions which can be called from QML by giving the Q_INVOKABLE flag and QVariant can be understood by QML/Javascript. Also you can provide some variables which can be read from QML by creating a Q_PROPERTY. For the case of my exposed ListModel, I needed the count value and the get() functions, so I made them and here is the code to add to Chris's class:

In the .cpp
QVariant ListModel::get(int row)
{
    ListItem * item = m_list.at(row);
    QMap<Qstring, Qvariant> itemData;
    QHashIterator<int, Qbytearray> hashItr(item->roleNames());
    while(hashItr.hasNext()){
        hashItr.next();
        itemData.insert(hashItr.value(),item->data(hashItr.key()).toString());
    }
    // Edit: 
    // My C++ is sometimes a bit rusty, I was deleting item... 
    // DO NOT delete item... otherwise you remove the item from the ListModel
    // delete item;
    return QVariant(itemData);
}

To get the count.

In the .h
public ListModel : public QAbstractListModel
{
    Q_OBJECT
    Q_PROPERTY( int count READ getCount() NOTIFY countChanged())

public:
    ...
    int getCount() { this->count = this->rowCount(); return count; }
    Q_INVOKABLE QVariant get(int row);
    ...
private:
    ...
    int count;
    ...
};

The get() function will create a QVariant which is like a Javascript object. So you can have later in your Javascript something like:

function useCppModel(){
    // We can check the number of item in our C++ Model now
    for(var i = 0; i < model.count; i++){
        // Here you use the get function declared as invokable
        // It's just like a real ListModel now ;)
        var item = model.get(i);
        // Now, you can then access the content of your item like item.property
    }
}

That's pretty much my addition to what Chris provided and this might inspire/help some of you. I hope you enjoyed. Sorry, if I'm not doing a complete tutorial, but I do not have much time.

P.S.: By the way, I'm now QtAmbassador. Yeah~ :D

EDIT: I corrected the bug with the first C++ code snippet.

4 comments:

  1. Thanks for the post.

    FYI on line 4 above:
    QMap itemData;

    should be

    ReplyDelete
  2. Thank You for your extension of the C++ ListModel example Benoît!

    Even one year after your post, this is still a valuable example.

    A couple of things could make this even more valuable:

    1) Provide complete files of your extension to Chris' files

    I'm getting an error:
    Error: NOTIFY signal 'countChanged' of property 'count' does not exist in class ListModel

    Having the complete files would prevent each person using your example from having to overcome these cut-and-paste issues.

    2) Include the Public Domain license notice in your complete files, as Chris did.

    This assures people that it is OK to use your example as a part of their production programs.

    3) Make your complete example of C++ ListModel with SQL persistent storage backend available

    I'm amazed how difficult it is to achieve persistent storage of data in QML! My QML app generates data that is displayed in a ListView of previous results. Trying to save this data between executions of the app is very difficult. I think what I need is exactly what you've done with C++ ListModel backed by SQL persistent storage.

    If you could make this example available, I'm sure many people would Thank You (and perhaps send donations 8-)

    Thank You Again for your extension to Chris' C++ ListModel!

    johnea

    ReplyDelete
    Replies
    1. I should be more explicit:

      If you can provide me with the complete example of C++ ListModel with SQL persistent storage backend, I _will_ send a donation 8-)

      I'm trying to complete a project for a client this week (before 2012-11-10) and need this functionality.

      Please contact me: consulting@johnea.net

      Delete