[Solved] Passing QAbstractItemModel subclasses to QML using a QObject property



  • Should it be possible to pass a (pointer to a) QAbstractItemModel subclass to QML using a property of an QObject?

    If the pointer is "published" directly as a context property it works as expected.
    However, if the pointer is "published" indirectly as a property of a QObject subclass (which is itself set as a context property) I'm unable to access the various roles.

    You'll find an example right below - shortened and inlined for better readability.

    @
    class QueryModel : public QSqlQueryModel
    {
    public:
    QueryModel(QObject *parent = 0) : QSqlQueryModel(parent)
    {
    QHash<int, QByteArray> roles;

        roles[Qt::UserRole + 0] = "caption";
        roles[Qt::UserRole + 1] = "description";
    
        setRoleNames(roles);
    
        setQuery("SELECT caption, description FROM table");
    }
    
    QVariant data(const QModelIndex &item, int role) const
    {
        if(role < Qt::UserRole)
        {
            return QSqlQueryModel::data(item, role);
        }
        else
        {
            return QSqlQueryModel::data(index(item.row(), role - Qt::UserRole));
        }
    }
    

    };
    @
    @
    class QueryData : public QObject
    {
    Q_OBJECT
    Q_PROPERTY(QString query READ query)
    Q_PROPERTY(QueryModel* model READ model)

    public:
    explicit QueryData(QObject *parent = 0) : QObject(parent), _model(new QueryModel) {}

    QString query()
    {
        return _model->query().lastQuery();
    }
    QueryModel* model()
    {
        return _model;
    }
    

    private:
    QueryModel* _model;
    };
    @
    @
    Rectangle {
    width: 200
    height: 180

    Text {
        anchors.bottom: parent.bottom
        text: queryData.query  // Does work
    }
    
    ListView {
        anchors.fill: parent
        model: queryModel      // Does work
     // model: queryData.model // Does not work
        delegate: Item {
            width:  100;
            height: 40;
    
            Column {
                Text { text: caption           } // Does work when using model: queryModel
                                                 // Does not work when using model: queryData.model
                                                 //    ReferenceError: Can't find variable: caption
    
                Text { text: model.description } // Does work when using model: queryModel
                                                 // Does not work when using model: queryData.model
                                                 //    Unable to assign [undefined] to QString text
            }
        }
    }
    

    }
    @
    @
    int main(int argc, char* argv[])
    {
    QApplication application(argc, argv);

    qRegisterMetaType<QueryModel*>("QueryModel*");
    
    QueryModel* queryModel = new QueryModel(&application);
    QueryData* queryData = new QueryData(&application);
    
    QDeclarativeView view;
    
    view.rootContext()->setContextProperty("queryModel", queryModel);
    view.rootContext()->setContextProperty("queryData", queryData);
    view.setSource(QUrl("Main.qml"));
    view.show();
    
    return(application.exec(&#41;&#41;;
    

    }
    @



  • Look "here":http://doc.qt.nokia.com/4.7-snapshot/qml-extending.html and specifically in the section that says:
    [quote]
    As long as the property type, in this case Person, is registered with QML the property can be assigned.
    QML also supports assigning Qt interfaces. To assign to a property whose type is a Qt interface pointer, the interface must also be registered with QML. As they cannot be instantiated directly, registering a Qt interface is different from registering a new QML type. The following function is used instead:
    [/quote]



  • I do not yet see how this is related to the problem.

    QAbstractItemModel has to be an known type to QML, otherwise none of the both cases above would work.
    QueryData (subclass of QObject) has to be a known type to QML, otherwise I couldn't access the query property.

    The paragraph right above the one you quoted explicitly states that

    bq. QML can set properties of types that are more complex than basic intrinsics like integers and strings. Properties can also be object pointers, Qt interface pointers, lists of object points, and lists of Qt interface pointers. As QML is typesafe it ensures that only valid types are assigned to these properties, just like it does for primitive types.

    bq. Properties that are pointers to objects or Qt interfaces are declared with the Q_PROPERTY() macro, just like other properties.



  • The Problem is that QueryModel is not registered with qml (which is what I wanted to point out with the quote (and the bold sentence)). Add this to your main.cpp
    @qmlRegisterType<QueryModel>("queryModel",1,0,"QueryModel");@
    And this to your qml:
    @import queryModel 1.0@
    And to answer the question you will undoubtedly have after that:
    You are missing a Q_OBJECT in QueryModel



  • Arrr... I thought the problem was that the model isn't applied correctly and I obviously misinterpreted the documentation. I have assumed that you will need to qmlRegisterType only if you want to create an instance of a type directly inside QML code (as the Person example shows) - and the fact that the QueryData::query property could be accessed without any problems confirmed my assumption.

    I still don't just yet see the necessity for qmlRegisterType in this particular case but I'll dig further into the source in a quiet moment and find out.

    However, adding qmlRegisterType has solved the problem - muchas gracias!



  • You're welcome. As I understand it, the qmlRegisterType just registers it with the qml engine and therefore makes it possible to assign it (keep in mind qml is type safe and if you just pass an object, the qml engine has no idea what it is). Also, I just tried
    @
    qmlRegisterType<QueryModel>();
    @ instead of the
    @qmlRegisterType<QueryModel>("queryModel",1,0,"QueryModel");@
    and it works, too, but you can't use it to instantiate new objects in qml. And you don't need import then.



  • I encountered a similar problem: I wanted my model to return another model and populate a Repeater inside of a Delegate. What I have done was:

    @
    class MyModel: public QAbstractListModel
    {
    ...
    public:

    Q_INVOKABLE QObject* returnSubmodelForData(int index)
    {
        return m_data->at(index)->subModel();
    }
    

    private:
    QList<Data*> m_data;
    };
    @

    @
    class Data: public QObject
    {
    ...
    public:
    SubModel* subModel() const
    { return m_subModel; }
    };
    @

    The m_subModel is derived from one of the AbstracModel classes.

    I have to inform QML about my model:
    @
    QDeclarativeContext* ctxt = m_declarativeView->rootContext();
    ctxt->setContextProperty("myModel",m_myModel);
    @

    And I can use it inside of QML:
    @
    ListView {
    ...
    model: myModel
    }
    @

    And this is how delegate is using submodel:
    @
    Component {
    /* not important delegate stuff*/

    Repeater {
        model: myModel.returnSubmodelForData(index)
    }
    

    }
    @

    Basically the trick here is to use Q_INVOKABLE function. These functions can be called from QML and as long as they return one of the QML "supported types":http://doc.qt.nokia.com/4.7-snapshot/qtbinding.html#supported-data-types it should work.

    Cheers,
    Konrad



  • Hi !

    I am trying to achieve this exact same thing. But it is not working as well. I can see that my method to get the model is called, the model is instantiated (it inherits QAbstractListModel) but rowCount and Data are never called.

    Any idea ? I have a GridView in a ListView

    SOLVED : I got it solved. Please note that the method you call from QML to get the model must return QObject* and not the class of your model.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.