thats_nice
thats_nice

Reputation: 31

How does one change the model property of a TableView in qml from c++

I am relatively new to QT, so any help would be greatly appreciated!

I am working on a Qt Quick Application, making use of the QQmlApplicationEngine for the UI. I made a subclass of QAbstractTableModel and implemented the necessary functions and successfully created and displayed a (singular) table on the Window.

Currently, how I linked the model in the QML file is by setting the context property of the root property of QQmlApplicationEngine.

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QSharedPointer>
#include <QQmlContext>

#include "tablecontroller.h"

int main(int argc, char *argv[])
{
  QGuiApplication app(argc, argv);

  QSharedPointer<QQmlApplicationEngine> engine = 
                     QSharedPointer<QQmlApplicationEngine>::create();

  TableController theController(engine.toWeakRef());

  engine.data()->rootContext()->setContextProperty("TableController", &theController);

  engine.data()->load(QUrl(QStringLiteral("qrc:/main.qml")));
  return app.exec();
}

tabcontroller.h

#ifndef TABLECONTROLLER_H
#define TABLECONTROLLER_H

#include <QObject>
#include <QQmlApplicationEngine>
#include <QWeakPointer>
#include <QHash>
#include <QList>

#include "tablemodel.h"

class TableController : public QObject
{
  Q_OBJECT
public:
    explicit TableController(QWeakPointer<QQmlApplicationEngine> Engine, QObject *parent = 0);

    Q_INVOKABLE void AddEntry();

signals:

public slots:

private:
  TE::TDT::TableModel m_TableModel;
  QList<QString> m_Headings;
};

#endif // TABLECONTROLLER_H

tabcontroller.cpp

#include "tablecontroller.h"
#include <QQmlContext>

TableController::TableController(QWeakPointer<QQmlApplicationEngine> Engine, QObject *parent) : QObject(parent)
{
  m_Headings << "Heading1" << "Heading2" << "Heading3" << "Heading4";
  m_TableModel.setColumnHeadings(m_Headings);

  Engine.data()->rootContext()->setContextProperty("myModel", &m_TableModel);
}

void TableController::AddEntry()
{
  QHash<QString, QVariant> tempHash;
  int counter = 1;
  for (auto x : m_Headings)
  {
    tempHash.insert(x, QString::number(counter));
    counter++;
  }
  m_TableModel.addElement(tempHash);
}

tablemodel.h

#ifndef TABLEMODEL_H
#define TABLEMODEL_H

#include <QObject>
#include <QAbstractItemModel>
#include <QModelIndex>
#include <QVariant>
#include <QList>
#include <QString>
#include <QHash>

namespace TE {

namespace TDT {

class TableModel : public QAbstractTableModel
{
  Q_OBJECT
  Q_PROPERTY(QStringList userRoleNames READ userRoleNames CONSTANT)
public:
    explicit TableModel(QObject *parent = 0);

    enum MyModelRoles {
        UserRole1 = Qt::UserRole + 1,
        UserRole2,
    };

  void setFirstColumn(const QList<QString> &FirstColumn);

  void setColumnHeadings(const QList<QString> &ColumnHeadings);

  void addElement(const QHash<QString, QVariant> Entry);

  QStringList userRoleNames();

signals:

public slots:

// QAbstractTableModel interface
public:
  int rowCount(const QModelIndex &parent) const override;
  int columnCount(const QModelIndex &parent) const override;
  QVariant data(const QModelIndex &index, int role) const override;
  QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
  QHash<int, QByteArray> roleNames() const override;

private:
  QList<QHash<QString, QVariant>> m_TableData;
  QList<QString> m_ColumnHeadings;
  QMap<int, QString> m_roleNames;

};

}// TDT

}// TE

#endif // TABLEMODEL_H

tablemodel.cpp

#include "tablemodel.h"
#include <QDebug>
#include <QAbstractListModel>

TE::TDT::TableModel::TableModel(QObject *parent) : QAbstractTableModel(parent)
{

}

int TE::TDT::TableModel::rowCount(const QModelIndex &parent) const
{
  Q_UNUSED(parent);
  return m_TableData.count();
}

int TE::TDT::TableModel::columnCount(const QModelIndex &parent) const
{
  Q_UNUSED(parent);
  return m_ColumnHeadings.count();
}

QVariant TE::TDT::TableModel::data(const QModelIndex &index, int role) const
{
  QVariant retVal;
  try {
     if(!index.isValid())
     {
         throw QString("Invalid index for inherited data function");
     }
     // Check row index
     if(index.row() >= m_TableData.count() || index.row() < 0)
     {
         throw QString("Index (row) out of bounds for data function");
     }
     //Check column index
     if(index.column() >= m_ColumnHeadings.count() || index.column() < 0)
     {
         throw QString("Index (column) out of bounds for data function");
     }

     QList<int> keys = m_roleNames.keys();

     if(role == Qt::DisplayRole || role == Qt::EditRole)
     {
         QString colKey = m_ColumnHeadings.at(index.column());
         if (m_TableData.at(index.row()).value(colKey).isNull())
         {
             retVal = QVariant();
         } else {
             retVal = m_TableData.at(index.row()).value(colKey);
         }
     } else if (m_roleNames.keys().contains(role)) {
         QHash<QString, QVariant> temp1 = m_TableData.at(index.row());
         retVal = m_TableData.at(index.row()).value(m_roleNames.value(role));
     }
     return retVal;

  } catch (QString &e) {
     qDebug() << e;
     return QVariant();
  }
}

QVariant TE::TDT::TableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
  Q_UNUSED(orientation);
  QVariant retVal;
  if (role == Qt::DisplayRole)
  {
     retVal = m_ColumnHeadings.at(section);
  }
  return retVal;
}

QHash<int, QByteArray> TE::TDT::TableModel::roleNames() const {
  // Populate the roles - basically the column headings
  QHash<int, QByteArray> roles = QAbstractTableModel::roleNames();

  // Should not overwrite existing roles
  int LastIndexOfUserRole = Qt::UserRole;
  for (int x = 1; x <= m_ColumnHeadings.count(); x++)
  {
     roles[LastIndexOfUserRole + x] = m_ColumnHeadings.at(x-1).toUtf8();
  }
  return roles;
}

QStringList TE::TDT::TableModel::userRoleNames() // Return ordered List of user-defined roles
{
  QHashIterator<int, QByteArray> i(roleNames());
  while (i.hasNext())
  {
     i.next();
     if(i.key() > Qt::UserRole)
     {
         m_roleNames[i.key()] = i.value();
     }
  }
  return m_roleNames.values();
}

void TE::TDT::TableModel::setColumnHeadings(const QList<QString> &ColumnHeadings)
{
  m_ColumnHeadings = ColumnHeadings;
}

void TE::TDT::TableModel::addElement(const QHash<QString, QVariant> Entry)
{
   beginInsertRows(QModelIndex(), this->rowCount(QModelIndex()), this->rowCount(QModelIndex()));
   m_TableData.append(Entry);
   endInsertRows();
}

main.qml import QtQuick 2.5 import QtQuick.Window 2.2 import QtQuick.Controls 1.2

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")
    id: mainwindow

    // template component for the column headings
    Component
    {
        id: columnComponent
        TableViewColumn{width: 100 }
    }

    TableView {
        id: tableview
        height: mainwindow.height - 50
        width: mainwindow.width
        y: 5
        x: 0
        visible: true
        resources:
        {
            var roleList = myModel.userRoleNames
            var temp = []
            for(var i = 0; i < roleList.length; i++)
            {
                var role  = roleList[i]
                temp.push(columnComponent.createObject(tableview, { "role": role, "title": role}))
            }
            return temp
        }
        model: myModel
    }

    Rectangle {
        id: abutton
        anchors.top: tableview.bottom
        height: 40
        width: mainwindow.width

        Text {
            text: "Click to Add"
            anchors.fill: parent
        }

        MouseArea {
            anchors.fill: parent
            onClicked: {
                TableController.AddEntry()
            }
        }
    }
}

Code adapted from: QML TableView with dynamic number of columns

Now, my question is, if I wanted to re-use the TableView as that defined in the main.qml I want to use another model for it. The problem is (from my limited understanding) that the "variable" that the model in the QML links to is static (defined at start-up), in this case, "myModel".

How can I change the model once I create another instance of this TableView? Will I have to link another "variable" every time?

I have tried to cast the TableView (in QML) to a QQuickItem (in c++) and trying to set the property there, to no avail (gives a null, but also QQuickItem doesn't have a "setModel" function)

Sorry for the long post, wanted to give as much information as possible.

Upvotes: 1

Views: 2631

Answers (3)

thats_nice
thats_nice

Reputation: 31

https://stackoverflow.com/a/35755172/7094339

This guy saved my life :D So it appears I needed to use the component.beginCreate() function.

Upvotes: 0

GrecKo
GrecKo

Reputation: 7150

If I understand your question correctly, you need to reuse your custom TableView with another model, you don't really need to change the model of your existing TableView.

For that you can define a new component in a separate file and use it elsewhere (some doc here : http://doc.qt.io/qt-5/qtqml-documents-definetypes.html)

In your case it could look like that :

DynamicTableView.qml :

TableView {
    //it's better not to set positioning properties in a component definition file.
    Component {
        id: columnComponent
        TableViewColumn { width: 100 }
    }  
    resources:
    {
        var roleList = model.userRoleNames // here you expect all your models to be an instance of your TableModel
        var temp = []
        for(var i = 0; i < roleList.length; i++)
        {
            var role  = roleList[i]
            temp.push(columnComponent.createObject(tableview, { "role": role, "title": role}))
        }
        return temp
    }
}

You could then reuse your component in main.qml and define its model property :

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")
    id: mainwindow    

    DynamicTableView {
        id: tableview
        height: mainwindow.height - 50
        width: mainwindow.width
        y: 5
        x: 0
        model: myModel
    }    

    Rectangle {
        id: abutton
        anchors.top: tableview.bottom
        height: 40
        width: mainwindow.width    

        Text {
            text: "Click to Add"
            anchors.fill: parent
        }    

        MouseArea {
            anchors.fill: parent
            onClicked: {
                TableController.AddEntry()
            }
        }
    }
}

Upvotes: 0

Kevin Krammer
Kevin Krammer

Reputation: 5207

As @folibis said, one option is to make your model instantiable from QML, i.e. so that you can craete instances of the model in QML.

You can then add a method to the TableController to "register" these instances in case the controller needs to be aware of them.

Alternatively you still create the models inside TableController and make them accessible, e.g. via one property for each model, a list property or a Q_INVOKABLE method.

Upvotes: 0

Related Questions