Access dynamic created ListModel elements from C++!



  • Hi all,

    how I can access from within C++ the elements of a ListModel?

    I write a application with a ListView. The content is set by an ListModel which is set dynamically during runtime. This is necessary because the number of elements is controlled by an config file loaded by the application.

    Any examples I found (Qt examples, google search, forums) uses always Models with fixed number of elements. That's bad!

    At first I used the VisualItemModel like
    @
    ...
    VisualItemModel {
    id: myModel
    objectName: "myModel"

        Rect1 {
        ...
        }
    
        Rect2 {
        ...
        }
    

    @
    But the model doesn't have any append() functions.

    The I changed to use a ListModel like that:
    @
    ...
    ListModel {
    id: myModel
    objectName: "myModel"

        ListElement {
            source: "Rect1.qml"
        }
        ListElement {
            source: "Rect2.qml"
        }
    

    ...
    @
    with a delegate like that:
    @
    Component {
    id: myDelegate
    Loader {
    source: model.source
    }
    }
    @
    That works fine.

    But the big question is how I can reference/get access to the list elements from C++?
    Items are only available trough its "objectName". How I can set the "objectName" of the elements?

    From C++ i tried this:
    @
    ...
    QObject* object = m_view->rootObject();
    QObject model = object->findChild<QObject>("myModel");
    if (model)
    ... // this is working!!
    ...
    QList<QObject*> list = model->findChildren<QObject*>("rect1"); // "rect1" is the 'objectName' in Rect1.qml
    if (list) // fails!!! No items where found with that name!!!
    ...
    @
    Have anybody an idea how I can get access to such dynamic created elements?

    Thanks a lot...
    jackmack

    [Edit: Please wrap code in @ tags in the future. Thanks! -- mlong]



  • Hi,

    there is no public API to conveniently access QML elements from C++. Even when using private impl. classes (a no go) it looks quite dirty as you can see in "tst_qquicklistmodel.cpp":http://qt.gitorious.org/qt/qtdeclarative/blobs/stable/tests/auto/qml/qquicklistmodel/tst_qquicklistmodel.cpp

    You could write simple QML functions that provide you access to the model elements and then call QMetaObject::invokeMethod from C++ as explained in "Interacting with QML Objects from C++":http://doc-snapshot.qt-project.org/qt5-stable/qtqml/qtqml-cppintegration-interactqmlfromcpp.html#invoking-qml-methods. This may be a practical way for smaller projects/tasks.

    But it seems to be much cleaner, you manage your model data completely in C++ and implement a reasonable QML/JS callable interface. Details are provided in "Integrating QML and C++":http://doc-snapshot.qt-project.org/qt5-stable/qtqml/qtqml-cppintegration-topic.html.

    Beware:
    I am not a QML expert. I just had a closer look at C++ interfacing too.



  • It is a counter-productive thing to access QML from C++. It defeats the whole purpose of UI and logic abstraction. The idea is to be able to access C++ functionality from C++ but not the other way around. Even though it is possible to like... get a pointer to a specific QML element you should not get in the practice of reaching from C++ into the QML world.



  • [quote author="utcenter" date="1363417941"]It is a counter-productive thing to access QML from C++. It defeats the whole purpose of UI and logic abstraction. The idea is to be able to access C++ functionality from C++ but not the other way around. Even though it is possible to like... get a pointer to a specific QML element you should not get in the practice of reaching from C++ into the QML world.[/quote]

    I know the purpose of UI and logic abstraction! And i try to do that!
    Based on that pupose I need a code example to imagine it by myself how I can do that. At the moment I don't know how I can solve my situation described above.

    Please give me an example how I can call a JS function of "Rect2.qml" from C++. That's typical the logic layer is changing somewhat and the UI has to show it.



  • You don't call from C++ into QML or JS. What you do is that you expose an object with a signal on it, and you emit that signal. Whatever happens in response to that, is out of the concern of your C++ code.



  • Adding a getChild function to your model first

    @
    ListModel {
    id: myModel
    objectName: "myModel"

        ListElement {
            source: "Rect1.qml"
        }
        ListElement {
            source: "Rect2.qml"
        }
        function getChild(index)
        {
            return get(index)
        }
    

    @

    Then from C++ you can access a model item this way

    @
    // get number of items
    QVariant count = QQmlProperty::read( model, "count" );
    if (count.isValid())
    qDebug() << "count: " << count;

    // get element #0
    QVariant index = 0;
    bool succeeded = QMetaObject::invokeMethod(
        model, "getChild", Q_RETURN_ARG(QVariant, retValue), Q_ARG( QVariant, index ) );
    if (succeeded)
    {
        const QObject *child = qvariant_cast<QObject *>( retValue );
    
        QVariant name = QQmlProperty::read( child, "name" );
        if (name.isValid())
            qDebug() << "child.name: " << name;
    }
    

    @

    Ugly as hell but it should work.



  • ->Roland_R
    The "ugly as hell" works. But there is an other issue! Communicate from QML to C++ doesn't work. I used an C++ exposed class with signals/slots.

    I think the used Loader component in the delegate creates the *.qml components/objects with their own context - not in the main.qml context. Therefore I don't have access to my exposed class. Strange: Nothing is written to the console like "unknown to find my exposed class". Only no calls of event handlers or calling methods works :-(

    Using of example:
    @(.h)
    ...
    signals:
    void enableComponent();
    ...

    (.cpp)
    ...
    emit enableComponent();
    ...

    (.qml)
    ...
    Connections {
    target: MyBinding
    onEnableComponent: {
    console.log("rect1")
    }
    }
    ...@

    To avoid this and have fully access uand functionality of exposes classes I have to use VisualItemModel instead of ListModel.

    ->Andre:
    Yes, I think this is the correct way. Using exposed classes can be uses for vice-versa communication QML/C++.

    But in my case there is an evil restriction of using VisualItemModel: No append(), remove() methods!

    For reminder:
    My application should be able to set the number of pages of a ListView depending of a xml config file which is loaded at runtime.

    VisualItemModel works only with static items like

    @main.qml

    VisualItemModel {
        id: myModel
        objectName: "myModel"
    
        Rect1 {}
    }
    

    @

    But know I found a hack/workaround:
    "Changing the model of the VisualItemModel" ;-)

    Calling a JS function at runtime from C++ like that:

    @ function changeModel()
    {
    var str;
    str = "import QtQuick 1.1\n
    Item {\n
    id: myComp\n
    property alias count: myModel.count\n
    property alias model: myModel\n
    \n
    VisualItemModel {\n
    id: myModel\n
    objectName: "myModel"\n
    Rect2 {}\n
    Rect3 {}\n
    }\n
    }"

        var o = Qt.createQmlObject(str, mainRect)
    
        view.model = o.model;
    }
    

    @

    That works!

    At runtime I can prepare a string containing the qml code and pass it to the changeModel() as parameter.

    Strange, but works... (await I will find next restrictions).



  • This works, but seems unnatural (or hacky/ugly :-) ) for me that the C++ code "reaches" into the QML world and depends on particular functions or named objects to be present.

    I don't know the full scope of requirements for your project, but would it be possible to implement the model in C++? Derive from QAbstractListModel:

    @
    class MyCppModel : public QAbstractListModel
    {
    Q_OBJECT
    public:
    explicit MyCppModel(QObject *parent = 0) : QAbstractListModel(parent) {}
    int rowCount(const QModelIndex & parent = QModelIndex()) const {
    return itemSources.count();
    }

    QVariant data(const QModelIndex &index, int role) const {
        if (role == ItemSourceRole)
           return itemSources.at(index.row());
       else
           return QVariant();
      }
    

    public slots:
    void append(QString itemSource) {
    beginInsertRows(itemSources.count(), itemSources.count());
    itemSources.append(itemSource);
    endInsertRows();
    }

    protected:
    enum Roles { ItemSourceRole : Qt::UserRoles + 1 }
    QHash<int, QByteArray> roleNames() const {
    QHash<int, QByteArray> roles;
    roles[ItemSourceRole] = "item_source";
    return roles;
    }
    private:
    QList<QString> itemSources;
    @

    in main.cpp:
    @qmlRegisterType<MyCppModel>("my.components", 1, 0, "MyCppModel");@

    in QML:
    @
    MyCppModel {
    id: myModel
    onComponentCompleted: {
    myModel.append("Rect1.qml")
    myModel.append("Rect2.qml")
    }
    }

    Column {
    Repeater {
    model: myModel
    Loader { source: item_source }
    }
    }
    @

    (Code not tested, just written to show the main idea)

    Since the model is in C++, any other C++ code could also call append() to add items to the model.
    This way you don't tie the C++ backend to any particular QML frontend.



  • Thanks. When I have more time I will check your suggestion to implement a C++ model.



  • Hi Torgeir, you are so cool to qmlRegisterType a customized C++ Model for QML usage.

    But it is much easier to @findChild@ by @objectName@ and @setProperty@ the @QQuickListView@ property model.

    Here is my example,

    in QML:
    @
    ...
    ListView {
    objectName: "categoryListView"
    model: categoryModel
    ...
    @

    in C++:
    @
    QQmlApplicationEngine engine(QUrl("qml/main.qml"));
    QObject *topLevel = engine.rootObjects().value(0);

    QObject *categoryListView = topLevel->findChild<QObject *>("categoryListView");
    qDebug() << categoryListView;
    QStringList dataList;
    for (int i = 1; i < 19; i++)
    dataList.append("Category " + QString::number(i));
    categoryListView->setProperty("model", QVariant::fromValue(dataList));
    @

    snapshot shown as https://www.dropbox.com/s/t03szfgyidohsoy/抓图2.png



  • [quote author="xiangzhai" date="1393923065"]Hi Torgeir, you are so cool to qmlRegisterType a customized C++ Model for QML usage.

    But it is much easier to @findChild@ by @objectName@ and @setProperty@ the @QQuickListView@ property model.
    [/quote]
    It depends on the situation what is easier. I think Torgeir is right that it is a bit weird to have to "reach into" the QML tree from C++. It is actually discouraged by the Qt documentation to reach deeply into the tree if you can avoid it. I find it more elegant to not have to depend on manually setting an objectName in the QML: way less brittle.

    I'd advise your approach for small toy projects and experiments, but Torgeirs approach for anything serious.



  • Hi Andre,

    Yes, it depends :)

    And I find another way to play with ListView model

    1. qmlRegisterType a customized C++ Model such as:
      @
      qmlRegisterType<MyModel>("cn.com.isoft.demo", 1, 0, "MyModel");
      @

    2. QStringList property such as:
      @
      Q_PROPERTY(QStringList myLists READ myLists NOTIFY myListsChanged)
      @

    3. put MyModel in QML such as:
      @
      import cn.com.i-soft.demo 1.0
      ...
      ListView {
      ...
      MyModel {
      id: myModel
      }

      model: myModel.myLists
      delegate: Rectangle {
      ...
      Text { text: modelData }
      }
      }
      @

    Regards,
    Leslie Zhai


Log in to reply
 

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