June 17, 2011

Lukas Geyer Lukas Geyer
Gene Splicer
2074 posts

[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.

  1. class QueryModel : public QSqlQueryModel
  2. {
  3. public:
  4.     QueryModel(QObject *parent = 0) : QSqlQueryModel(parent)
  5.     {
  6.         QHash<int, QByteArray> roles;
  7.  
  8.         roles[Qt::UserRole + 0] = "caption";
  9.         roles[Qt::UserRole + 1] = "description";
  10.  
  11.         setRoleNames(roles);
  12.  
  13.         setQuery("SELECT caption, description FROM table");
  14.     }
  15.  
  16.     QVariant data(const QModelIndex &item, int role) const
  17.     {
  18.         if(role < Qt::UserRole)
  19.         {
  20.             return QSqlQueryModel::data(item, role);
  21.         }
  22.         else
  23.         {
  24.             return QSqlQueryModel::data(index(item.row(), role - Qt::UserRole));
  25.         }
  26.     }
  27. };

  1. class QueryData : public QObject
  2. {
  3.     Q_OBJECT
  4.     Q_PROPERTY(QString query READ query)
  5.     Q_PROPERTY(QueryModel* model READ model)
  6.  
  7. public:
  8.     explicit QueryData(QObject *parent = 0) : QObject(parent), _model(new QueryModel) {}
  9.  
  10.     QString query()
  11.     {
  12.         return _model->query().lastQuery();
  13.     }
  14.     QueryModel* model()
  15.     {
  16.         return _model;
  17.     }
  18.  
  19. private:
  20.     QueryModel* _model;
  21. };

  1. Rectangle {
  2.     width: 200
  3.     height: 180
  4.  
  5.     Text {
  6.         anchors.bottom: parent.bottom
  7.         text: queryData.query  // Does work
  8.     }
  9.  
  10.     ListView {
  11.         anchors.fill: parent
  12.         model: queryModel      // Does work
  13.      // model: queryData.model // Does not work
  14.         delegate: Item {
  15.             width:  100;
  16.             height: 40;
  17.  
  18.             Column {
  19.                 Text { text: caption           } // Does work when using model: queryModel
  20.                                                  // Does not work when using model: queryData.model
  21.                                                  //    ReferenceError: Can't find variable: caption
  22.  
  23.                 Text { text: model.description } // Does work when using model: queryModel
  24.                                                  // Does not work when using model: queryData.model
  25.                                                  //    Unable to assign [undefined] to QString text
  26.             }
  27.         }
  28.     }
  29. }

  1. int main(int argc, char* argv[])
  2. {
  3.     QApplication application(argc, argv);
  4.  
  5.     qRegisterMetaType<QueryModel*>("QueryModel*");
  6.  
  7.     QueryModel* queryModel = new QueryModel(&application);
  8.     QueryData* queryData = new QueryData(&application);
  9.  
  10.     QDeclarativeView view;
  11.  
  12.     view.rootContext()->setContextProperty("queryModel", queryModel);
  13.     view.rootContext()->setContextProperty("queryData", queryData);
  14.     view.setSource(QUrl("Main.qml"));
  15.     view.show();
  16.  
  17.     return(application.exec());
  18. }

7 replies

June 17, 2011

loladiro loladiro
Lab Rat
596 posts

Look here [doc.qt.nokia.com] and specifically in the section that says:


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:

June 17, 2011

Lukas Geyer Lukas Geyer
Gene Splicer
2074 posts

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

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.

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

June 18, 2011

loladiro loladiro
Lab Rat
596 posts

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

  1. qmlRegisterType<QueryModel>("queryModel",1,0,"QueryModel");

And this to your qml:
  1. import queryModel 1.0

And to answer the question you will undoubtedly have after that:
You are missing a Q_OBJECT in QueryModel

June 18, 2011

Lukas Geyer Lukas Geyer
Gene Splicer
2074 posts

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!

June 18, 2011

loladiro loladiro
Lab Rat
596 posts

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

  1. qmlRegisterType<QueryModel>();
instead of the
  1. 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.

June 18, 2011

bergo.torino bergo.torino
Lab Rat
41 posts

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:

  1. class MyModel: public QAbstractListModel
  2. {
  3. ...
  4. public:
  5.  
  6.     Q_INVOKABLE QObject* returnSubmodelForData(int index)
  7.     {
  8.         return m_data->at(index)->subModel();
  9.     }
  10.  
  11. private:
  12.     QList<Data*> m_data;
  13. };

  1. class Data: public QObject
  2. {
  3. ...
  4.  public:  
  5.     SubModel* subModel() const
  6.     { return m_subModel; }
  7. };

The m_subModel is derived from one of the AbstracModel classes.

I have to inform QML about my model:

  1.     QDeclarativeContext* ctxt = m_declarativeView->rootContext();
  2.     ctxt->setContextProperty("myModel",m_myModel);

And I can use it inside of QML:

  1. ListView {
  2.     ...
  3.     model:  myModel
  4. }

And this is how delegate is using submodel:

  1. Component {
  2.    /* not important delegate stuff*/
  3.  
  4.     Repeater {
  5.         model: myModel.returnSubmodelForData(index)
  6.     }
  7. }

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 [doc.qt.nokia.com] it should work.

Cheers,
Konrad

 Signature 

It’s compiling - mostly I’m slacking off ;)

October 31, 2011

Guillaume Guillaume
Lab Rat
4 posts

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.

 
  ‹‹ Ownership between Qt C++ and QML      [solved] terminate application ››

You must log in to post a reply. Not a member yet? Register here!