Expose C++ Submodel to QML
-
Hello. I want to display items of the model inherited from
QStandardItemModel
and its subitems in two differentListViews
. How can I expose a submodel to QML? -
@PavloPonomarov if want to expose c++ model to QML then we have do like this in main .cpp
QStringListModel *m_LisModel=new QStringListModel(); QQmlContext *context1=engine.rootContext(); context1->setContextProperty("model1",m_LisModel);
-
@PavloPonomarov Hi, I am not sure if I have understood but what I think you are asking about is the situation where you have a model, where the item data of the model is itself a model. The way to expose this to QML is to wrap the pointer to the sub-model in a
QVariant
. You can then access the data item of the parent model, using the sub-model's role name, and use that item as the model of aListView
or whatever.This SO question might help:
https://stackoverflow.com/questions/29558138/use-a-qabstractlistmodel-in-another-one
-
I registered my model using
qmlRegisterType
. How do I expose the submodel?
For example I fill my model like this:for(int i = 0; i < 5; i++){ QStandardItem *item = new QStandardItem; item->setData("Param"+QString::number(i+1), Name); item->setData(i, Value); appendRow(item); } QStandardItem *item3 = item(2,0); for(int i = 0; i < 8; i++){ QStandardItem *sub = new QStandardItem; sub->setData("Sub"+QString::number(i), Name); sub->setData(100+i, Value); item3->appendRow(sub); }
Now I want to display model items in one ListView and subitems in another ListView. First ListView is no problem
ListView{ model: myModel delegate: myDelegate{} }
But I have problems getting subitems. In C++ I can get
QStandardItem*
of each subitem, but this type is unknown to QML and will be translated asQVariant(QStandardItem*, )
and can't be used by ListView. I also can't get it from first ListView as its QMLmodel
isQQmlDMAbstractItemModelData
, I haven't found any way to get its children. -
@Bob64 My bad, I call it submodel but actually I meant subitems, like I noted in example in my previous post. This SO question is one way to do that, but in my case not every item may have subitems or may have different number of subitems. If I'll create a model for subitems and in parent model add role for it, means each model item will have subitems(submodel). Am I right?
-
@PavloPonomarov Yes, I realised what you meant after your previous post but I didn't have anything clever to respond with I am afraid. I see that you want to be able to expose what is effectively a list of subitems to a
ListView
as if it were a model. I don't know if there is a way to do that directly (I am a relative newbie and I am not even familiar withQStandardItem
), but QML does tend to be quite flexible in terms of what it will admit as a model for aListView
. Perhaps someone with more experience will be able to suggest a way to do it."If I'll create a model for subitems and in parent model add role for it, means each model item will have subitems(submodel). Am I right?"
Yes, I think that is the same as what I was thinking. But there might be an easier way - I don't know.
-
@Bob64
If you can guarantee the lifetime of an object to always be there then setContextProperty is fine. However, if that object gets deleted then any access to that property from QML will cause the program to crash. For this reason I have started creating C++ objects and then allowing them to be instantiated in QML like so:class SomeObject : public QObject // or any QObject based class { };
Then in main.cpp (or other suitable place):
qmlRegisterType<SomeObject>("SomeObjects",1,0,"SomeObject");
Then in qml:
import SomeObjects 1.0 ~ SomeObject { id: someobject }
Now the lifetime of the object is controlled by the QML code. This may not may not be what you want.
@PavloPonomarov
Please post a complete example of the class you are creating. It is difficult to tell what you are trying to expose and where. -
@fcarney
mymodel.h#ifndef MYMODEL_H #define MYMODEL_H #include <QObject> #include <QStandardItemModel> #include <QStandardItem> class MyModel : public QStandardItemModel { Q_OBJECT public: explicit MyModel(QObject *parent = nullptr); enum{ Name =Qt::UserRole, Value }; Q_INVOKABLE void setValue(const QModelIndex &index, double value); Q_INVOKABLE double getValue(const QModelIndex &index); Q_INVOKABLE QModelIndex getIndex(int row); Q_INVOKABLE QModelIndex getChildIndex(int parentRow, int childRow); Q_INVOKABLE QStandardItem *getItem(int row); QHash<int, QByteArray> roleNames() const override; QVector<MyModel> mItems; signals: }; #endif // MYMODEL_H
mymodel.cpp
#include "mymodel.h" MyModel::MyModel(QObject *parent) : QStandardItemModel(parent) { for(int i = 0; i < 5; i++){ QStandardItem *item = new QStandardItem; item->setData("Param"+QString::number(i+1), Name); item->setData(i, Value); appendRow(item); } QStandardItem *item3 = item(2,0); for(int i = 0; i < 8; i++){ QStandardItem *sub = new QStandardItem; sub->setData("Sub"+QString::number(i), Name); sub->setData(100+i, Value); item3->appendRow(sub); } } void MyModel::setValue(const QModelIndex &index, double value) { itemFromIndex(index)->setData(value, Value); } double MyModel::getValue(const QModelIndex &index) { return itemFromIndex(index)->data(Value).toDouble(); } QModelIndex MyModel::getIndex(int row) { return index(row, 0); } QModelIndex MyModel::getChildIndex(int parentRow, int childRow) { return index(parentRow, 0).child(childRow, 0); } QStandardItem* MyModel::getItem(int row) { return item(row); } QHash<int, QByteArray> MyModel::roleNames() const { QHash<int, QByteArray> names; names[Name] = "name"; names[Value] = "value"; return names; }
main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "mymodel.h" Q_DECLARE_METATYPE(QStandardItem*) int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); qRegisterMetaType<QStandardItem*>(); qmlRegisterType<MyModel>("test.io.myModel", 1, 0, "MyModel"); QQmlApplicationEngine engine; const QUrl url(QStringLiteral("qrc:/main.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); }
main.qml
import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Dialogs 1.2 import QtQuick.Controls 2.12 import QtQuick.Controls 1.4 import QtQml.Models 2.11 import QtQuick.Layouts 1.12 import test.io.myModel 1.0 Window { id: window width: 410 height: 280 visible: true property var objects: [] MyModel{ id: myModel } RowLayout{ height: parent.height TableView{ id: leftView Layout.fillHeight: true model: myModel TableViewColumn{ title: "Name" role: "name" } onClicked: { //MyModel.getItem(row) will return QVariant(QStandardItem*, ) which can't be used as a model rightView.model = myModel.getItem(row); } } TableView{ id: rightView Layout.fillHeight: true TableViewColumn{ title: "Name" role: "name" } } } }
In this example third model item has subitems. I need to expose them to QML and display in rightView
-
@PavloPonomarov said in Expose C++ Submodel to QML:
QStandardItem
I am not sure if this is a good fit for QML. QStandardItem either needs to be subclassed to provide methods to interact with QML. Or create a wrapper model for QStandardItem that will pull data out. You might be better off creating models that return other models to do what you want. Are you just trying to get a list of items inside QStandardItem?
-
@fcarney
Not only get the list but also use it to bind via Qml delegate item models to other components. In my case I guess wrapper model is not an option because any model item can have subitems and if I'll have to reference submodel inside my model (like @Bob64 mentioned) then each item of my model will have subitems. Guess I should try to subclass QStandardItem. -
@PavloPonomarov said in Expose C++ Submodel to QML:
Guess I should try to subclass QStandardItem.
Don't, you will regret it. Implement your own QAbstractListModel instead.
QStandardItem* is only good for the most basic of use-case, and even then I won't recommend it because its API is awkward to use. -
This post is deleted!
-
-
@GrecKo This article in documentation returns me to the same question I had at the beginning - having QAbstractListModel subclass how do I add subitems to items and expose them to QML? I found only one good example, that additionally to subclassing QAbstractListModel also creates a custom class for items. That means that I'll have to rewrite my whole QStandardItemModel subclass just because QML developers haven't added a better support for multi-level models. Is there no easier way than this?
-
@PavloPonomarov Sometimes, when a method is running into brick walls, you have to start over. That is often the fast route. I ran into similar issues with a Python app I wrote. It worked fine for years, but as I tried to add/modify things the ecosystem around my app degraded. It got to the point where it was a roll of the dice to build an exe properly. At that point I realized for longevity I need to rewrite the app in a different development ecosystem. I am now rewriting that app in Qt and C++. It is the faster route, though at first it looks like the long road.
For another app I am writing I have restructured the data models at least 4 or 5 times. I keep running into brick walls. Mostly due to my lack of understanding of the objects being used. You will not doubt discover some really neat approaches by reworking your models. You may even find better ways to approach the problem. In essence that is what programmers do. They figure out how to solve problems. They don't code, they don't use pretty apis that always do what we want them to do. We use duct tape, welding equipment, and super glue to make things do what we want. Sometimes the fumes make us sick, but we muddle through.
Take a hard look at the problem you are trying to solve. Is your mind making this more complex than it is? I always have to take time to rethink approaches. For instance, I was designing a set of classes to handle displaying a tank map on the screen. I was having trouble contemplating how to sync the data with the database. I realized I needed to make the database primary store and have everything else get fed by events from the database. When I did that I realized I can build all my "sub models" as ListModels in QML. I will expose my database as a QML object (QObject) with methods for adding/removing/changing entries in the database. Everything else will flow to the ListModels through signals. I scrapped about 10 C++ classes as a result.
Take a hard look at what classes are in QML and C++ inside Qt. Problems we are having are often already solved by knowing the tools in the toolbox. I don't know how many times I go to look for something and Qt already has a version of it I can use.
-
having QAbstractListModel subclass how do I add subitems to items and expose them to QML?
Return a list of your subitems in one of your base model role, I don't see the issue here.
Depending on your needs you could return a simple list like a QVariantList or QList<QObject*>, or a proper QAbstractListModel. -
@fcarney This video shows how to using C++ models with Qt Quick views.
https://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html -
@PavloPonomarov said in Expose C++ Submodel to QML:
How can I expose a submodel to QML?
I never used
QStandardItemModel
, so my reply is about the title of the topic "Expose C++ submodel to QML".I use since many years now a template class create by Thomas Boutrou which creates an
QAbstractListModel
by introspection of the base QObject.
Take a look at Qt QML Models and Qt SuperMacros.Perhaps this could help you
-
@GrecKo Same as for QAbstractListModel I could add such QVariantMap as additional role to QStandardItemModel. I had to create a slot that updates this role on
dataChanged
signal. Then I was able to get this map in QML. The issue here is that when I return this map from C++ it loses its connection to model and won't be updated in QML when the model is changed