1

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.

Community
  • 1
  • 1
thats_nice
  • 21
  • 4
  • 1
    You can expose your model to QML with [qmlRegisterType](http://doc.qt.io/qt-5/qqmlengine.html) and so create several model items – folibis Nov 01 '16 at 16:07

3 Answers3

0

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.

Kevin Krammer
  • 5,159
  • 2
  • 9
  • 22
0

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()
            }
        }
    }
}
GrecKo
  • 6,615
  • 19
  • 23
  • Thank you for your answer, but I still do not see how this solves my problem. I want to create multiple instances of the TableView, all having their own **unique** models, defined by the QAbstractTableModel sub-class defined in the c++ code. How do I set the model for each instance? Say if I wanted to create 2 (or any arbitrary number of) tables in one "tab", that would mean instantiating the TableView twice (or more), how will I set the models then? – thats_nice Nov 02 '16 at 09:58
  • Like that ? `DynamicTableView { id: firstTableview; model: myModel1; ... } DynamicTableView { id: secondTableview; model: myModel2; ... }` – GrecKo Nov 02 '16 at 10:27
  • But that requires me to hard code the models... As the title of the question says, how does one do it form c++? The application that I am writing lets the user insert a Table (or tables), and the user will be able to choose what parameters he wants to be in the table (hence the dynamic setting of the model). Sorry if I did not explain this properly in the question. EDIT: I am also trying to maintain the MVC architecture, I don't want to do too much QML modifying from the c++, in this case, I want to minimise "inserting" code into the QML file, I merely want to "set" the model. – thats_nice Nov 02 '16 at 10:55
  • The simplest way is to instantiate the model in QML, like suggested by @folibis and myself. Or, also as suggested before, you have a list of models and create one view for each list entry – Kevin Krammer Nov 02 '16 at 12:21
  • @GrecKo instead of the script code for `resources` you could likely also use an `Instantiator`. Should theoretically even allow dynamic column changes at runtime – Kevin Krammer Nov 02 '16 at 12:24
  • @KevinKrammer I just copied OP's code for `resources`, I also thought of using an `Instantiator` but you there's no `addColumn` function in `TablewView`, so it cannot really be done. – GrecKo Nov 02 '16 at 13:43
  • But yeah you should expose a list of model from c++ and instantiates a `TableView` for each with a `Repeater` or `ListView`. I don't think you should instantiate a model from QML unless you want to handle your business logic in QML, I prefer to do that in c++ – GrecKo Nov 02 '16 at 13:46
  • On my offline doc, there's is no `addColumn` function for `TableView`, but it's documented in the online one : http://doc.qt.io/qt-5/qml-qtquick-controls-tableview.html#addColumn-method – GrecKo Nov 02 '16 at 13:55
  • @GrecKo there is also insertColumn which even takes the index into account and removeColumn for the `Instantiator`'s onObjectRemoved. But yet, I see now that this was part of the question's code :) – Kevin Krammer Nov 02 '16 at 16:23
0

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

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

Community
  • 1
  • 1
thats_nice
  • 21
  • 4