Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. Expose C++ Submodel to QML
Forum Updated to NodeBB v4.3 + New Features

Expose C++ Submodel to QML

Scheduled Pinned Locked Moved Unsolved QML and Qt Quick
24 Posts 7 Posters 3.4k Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • KroMignonK KroMignon

    @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

    P Offline
    P Offline
    PavloPonomarov
    wrote on last edited by
    #21

    @KroMignon said in 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.

    Thank you. But I think those templates won't help in my case. I haven't found any possibility to build a multi-level model from the template

    1 Reply Last reply
    0
    • P PavloPonomarov

      @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

      GrecKoG Offline
      GrecKoG Offline
      GrecKo
      Qt Champions 2018
      wrote on last edited by
      #22

      @PavloPonomarov said in Expose C++ Submodel to 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

      Maybe you did something wrong there. Not sure about what you said about the slot and dataChanged signal, if that is done in QML, that's definitely not the correct way to do things. Anyway, using QStandardItemModel is fine for very basic PoC, if you need something more, it will get in the way and be more complicated than implementing your own model.

      @PavloPonomarov said in Expose C++ Submodel to QML:

      Thank you. But I think those templates won't help in my case. I haven't found any possibility to build a multi-level model from the template

      If you have a QQmlObjectListModel of A and the class A has a property of type QQmlObjectListModel, you know have a multi-level model. I've done this multiple times.

      I can't really help you more than that if you don't post a small self-sufficient code reproducing the problems you met.

      P 1 Reply Last reply
      0
      • GrecKoG GrecKo

        @PavloPonomarov said in Expose C++ Submodel to 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

        Maybe you did something wrong there. Not sure about what you said about the slot and dataChanged signal, if that is done in QML, that's definitely not the correct way to do things. Anyway, using QStandardItemModel is fine for very basic PoC, if you need something more, it will get in the way and be more complicated than implementing your own model.

        @PavloPonomarov said in Expose C++ Submodel to QML:

        Thank you. But I think those templates won't help in my case. I haven't found any possibility to build a multi-level model from the template

        If you have a QQmlObjectListModel of A and the class A has a property of type QQmlObjectListModel, you know have a multi-level model. I've done this multiple times.

        I can't really help you more than that if you don't post a small self-sufficient code reproducing the problems you met.

        P Offline
        P Offline
        PavloPonomarov
        wrote on last edited by
        #23

        @GrecKo said in Expose C++ Submodel to QML:

        Not sure about what you said about the slot and dataChanged signal

        I've added a new role for the QVariantMap into my model, but to keep it up-to-date with model changes I had to create a slot connected to dataChanged of the model. It looks like this:

        void MyModel::updateMap(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
            bool changed = false;
            if(roles[0] == ItemObject) return; //ItemObject is a role
            QVariantMap item = topLeft.data(ItemObject).toMap();
            item[roleNames[roles[0]]] = topLeft.data(roles[0]); //roleNames[roles[0]] to get string representation
            itemFromIndex(topLeft)->setData(item, ItemObject);
        }
        

        Then I wrote an invokable function to get this map:

        QVariant MyModel::getItem(const QModelIndex &index){
            return index.data(ItemObject);
        }
        

        And this is where the issue appears. I can call this function from QML, but is will return only current state of the map, without any binding.

        @GrecKo said in Expose C++ Submodel to QML:

        I can't really help you more than that if you don't post a small self-sufficient code reproducing the problems you met.

        I will try to write a small example to my problem

        1 Reply Last reply
        0
        • P Offline
          P Offline
          PavloPonomarov
          wrote on last edited by
          #24

          Here is the example:

          main.cpp

          #include <QGuiApplication>
          #include <QQmlApplicationEngine>
          #include <QQmlContext>
          #include "mymodel.h"
          
          int main(int argc, char *argv[])
          {
              QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
          
              QGuiApplication app(argc, argv);
          
              QQmlApplicationEngine engine;
              QQmlContext *context = engine.rootContext();
              myModel *model = new myModel();
              context->setContextProperty("myModel", model);
              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();
          }
          

          mymodel.h

          #ifndef MYMODEL_H
          #define MYMODEL_H
          
          #include <QStandardItemModel>
          #include <QObject>
          #include <QDebug>
          
          #define ROWS 10
          #define NESTED 3
          #define SUBITEMS 8
          
          class myModel : public QStandardItemModel
          {
              Q_OBJECT
          public:
              myModel();
              void fillModel();
          
              QVariantMap getObject(const QModelIndex &index);
              QVariantMap getObject(QStandardItem *item);
              Q_INVOKABLE QVariant getQMLObject(const QModelIndex &index);
              Q_INVOKABLE void setValue(const QModelIndex &index, int value);
              enum Roles{
                  Name=Qt::UserRole+1,
                  Value,
                  Description,
                  QMLObject
              };
          public slots:
              void updateObject(const QModelIndex &left, const QModelIndex &right, const QVector<int> &roles);
          private:
               QHash<int, QByteArray> roleNames;
               QHash<int,QVector<QPersistentModelIndex>> m_Objects;
          };
          
          #endif // MYMODEL_H
          

          mymodel.cpp

          #include "mymodel.h"
          
          myModel::myModel()
          {
              roleNames[Name] = "name";
              roleNames[Value] = "value";
              roleNames[Description] = "description";
              roleNames[QMLObject] = "qmlObject";
              setItemRoleNames(roleNames);
              fillModel();
              connect(this, &QAbstractItemModel::dataChanged, this, &myModel::updateObject);
          }
          
          void myModel::fillModel()
          {
              for(int i = 0; i < 3; i++){
                  QStandardItem *item = new QStandardItem();
                  item->setData("TreeParameter"+QString::number(i+1), Name);
                  item->setData(i, Value);
                  item->setData("Tree item"+QString::number(i+1), Description);
                  item->setData(getObject(item), QMLObject);
                  for(int j = 0; j < 8; j++){
                      QStandardItem *subItem = new QStandardItem();
                      subItem->setData("SubItem"+QString::number(j+1), Name);
                      subItem->setData(j, Value);
                      subItem->setData("Nested item"+QString::number(j+1), Description);
                      subItem->setData(getObject(subItem), QMLObject);
                      item->appendRow(subItem);
                  }
                  this->appendRow(item);
              }
              for(int i = 3; i < 10; i++){
                  QStandardItem *item = new QStandardItem();
                  item->setData("Parameter"+QString::number(i+1), Name);
                  item->setData(i, Value);
                  item->setData("Usual item"+QString::number(i+1), Description);
                  item->setData(getObject(item), QMLObject);
                  this->appendRow(item);
              }
          }
          
          QVariantMap myModel::getObject(const QModelIndex &index)
          {
              return getObject(itemFromIndex(index));
          }
          
          QVariantMap myModel::getObject(QStandardItem *item)
          {
              QVariantMap result;
              result[roleNames[Name]] = item->data(Name);
              result[roleNames[Value]] = item->data(Value);
              result[roleNames[Description]] = item->data(Description);
              return result;
          }
          
          QVariant myModel::getQMLObject(const QModelIndex &index)
          {
              return index.data(QMLObject);
          }
          
          void myModel::setValue(const QModelIndex &index, int value)
          {
              setData(index, value, Value);
          }
          
          void myModel::updateObject(const QModelIndex &left, const QModelIndex &right, const QVector<int> &roles)
          {
              if(roles[0] == QMLObject) return;
              QVariantMap obj = left.data(QMLObject).toMap();
              obj[roleNames[roles[0]]] = left.data(roles[0]);
              setData(left, obj, QMLObject);
          }
          

          main.qml

          import QtQuick 2.12
          import QtQuick.Window 2.12
          import QtQuick.Layouts 1.12
          import QtQuick.Controls 1.4 as QC1
          import QtQuick.Controls 2.12
          import QtQml.Models 2.11
          
          Window {
              width: 640
              height: 280
              visible: true
              title: qsTr("Hello World")
              RowLayout{
                  anchors.fill: parent
                  QC1.TreeView{
                      id: tree
                      Layout.fillWidth: true;
                      Layout.fillHeight: true;
                      model: myModel
                      selection: ItemSelectionModel{
                          id:mySelectionModel
                          model: myModel
                      }
                      itemDelegate: ItemDelegate{
                          Text{
                              text: styleData.value
                          }
                          MouseArea{
                              anchors.fill: parent
                              onClicked:{
                                  tree.selection.setCurrentIndex(styleData.index,ItemSelectionModel.SelectCurrent);
                                  target.modelItem = myModel.getQMLObject(styleData.index);
                                  target.modelIndex = styleData.index;
                              }
                          }
                      }
                      QC1.TableViewColumn{
                          width: 150
                          title: "Name"
                          role: "name"
                      }
                      QC1.TableViewColumn{
                          width: 50
                          title: "Value"
                          role: "value"
                          delegate: TextEdit{
                              text: styleData ? styleData.value : "";
                              Keys.onReturnPressed: {
                                  focus = false;
                                  myModel.setValue(styleData.index, text);
                              }
                          }
                      }
                      QC1.TableViewColumn{
                          width: 150
                          title: "Description"
                          role: "description"
                      }
                  }
                  RowLayout{
                      id: target
                      Layout.fillWidth: true;
                      property var modelIndex: false
                      property var modelItem: false
                      Rectangle{
                          implicitHeight: 20
                          implicitWidth: 65
                          border.color: "black";
                          border.width: 1
                          TextEdit{
                              id: targetValue
                              anchors.fill: parent
                              horizontalAlignment: Text.AlignHCenter
                              verticalAlignment: Text.AlignVCenter
                              text: target.modelItem ? target.modelItem.value : "NaN"
                              Keys.onReturnPressed: {
                                  targetValue.focus = false;
                                  if(target.modelIndex) myModel.setValue(target.modelIndex, targetValue.text);
                              }
                          }
                      }
                      TextArea{
                          id: targetText
                          readOnly: true
                          implicitWidth: 130
                          wrapMode: Text.Wrap
                          text: target.modelItem ? target.modelItem.name : "Nothing yet"
                      }
                  }
              }
          }
          

          Guess I'll have to explain it a little bit. When I click an item of the model in TreeView its QMLObject role is sent to target component, where Value is given to TextEdit and Name is given to TextArea along with the QModelIndex of selected item. So when I change the value inside of TextEdit it updates the value in model. The issue here is that if I change the value inside TreeView it won't be updated in TextEdit, because target receives only a copy of QMLObject role through return index.data(QMLObject);. I know about possibility to pass QQmlDMAbstractItemModelData from TreeView to the taget, but I'm trying to bind model item to target directly, this TreeView is only to make this example more clear. Sorry, couldn't think of something smaller, guess I'm too deep in this topic to keep it simple

          1 Reply Last reply
          0

          • Login

          • Login or register to search.
          • First post
            Last post
          0
          • Categories
          • Recent
          • Tags
          • Popular
          • Users
          • Groups
          • Search
          • Get Qt Extensions
          • Unsolved