Access dynamic created ListModel elements from C++!
-
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.
-
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. -
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
-
qmlRegisterType a customized C++ Model such as:
@
qmlRegisterType<MyModel>("cn.com.isoft.demo", 1, 0, "MyModel");
@ -
QStringList property such as:
@
Q_PROPERTY(QStringList myLists READ myLists NOTIFY myListsChanged)
@ -
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 -