I want to add to a QML table view a certain number of rows by pressing a button. The UI looks like this:

enter image description here

After pressing the "Update List Model" a new row should appear in the TableView.

My code looks like this (the bellow). I guess the addPerson method has to emit a dataChanged event to get this working. How can I do this? Or is there a better solution to sync a QML table view with a C++ model?

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>

#include "MainWindow.h"
int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);

    qmlRegisterType<TableModel>("TableModel", 0, 1, "TableModel");

    QQmlApplicationEngine engine;
    MainWindow mainWindow;

    return app.exec();
}

MainWindow.h

#pragma once

#include <QQmlApplicationEngine>
#include <QtQuick>

#include "TableModel.h"

class MainWindow : public QObject {
    Q_OBJECT;

public:
    explicit MainWindow() {
        engine_.load(QUrl(QStringLiteral("qrc:/main.qml")));

        QObject *rootObject = engine_.rootObjects().first();
        QObject::connect(rootObject, SIGNAL(on_ButtonUpdateListModel_click()), this, SLOT(on_ButtonUpdateListModel_click()));
    }

public slots:

    void on_ButtonUpdateListModel_click() {         
        QQuickView view;
        QQmlContext *ctxt = view.rootContext();
        model_.addPerson();
        ctxt->setContextProperty("myModel", &model_);
    }

private:
    TableModel model_;
    QQmlApplicationEngine engine_;
};

TableModel.h

#pragma once

#include <QAbstractTableModel>
#include <QObject>

class TableModel : public QAbstractTableModel {
    Q_OBJECT;
    enum TableRoles { TableDataRole = Qt::UserRole + 1, HeadingRole };

public:
    explicit TableModel(QObject *parent = nullptr) : QAbstractTableModel(parent) {
        table.append({
          "First Name",
          "Last Name",
          "Age",
        });
    }

    int rowCount(const QModelIndex & = QModelIndex()) const override {
        return table.size();
    }

    int columnCount(const QModelIndex & = QModelIndex()) const override {
        return table.at(0).size();
    }

    QVariant data(const QModelIndex &index, int role) const override {
        switch (role) {
        case TableDataRole: {
            return table.at(index.row()).at(index.column());
        }
        case HeadingRole: {
            if (index.row() == 0) {
                return true;
            } else {
                return false;
            }
        }
        default: break;
        }

        return QVariant();
    }

    QHash<int, QByteArray> roleNames() const override {
        QHash<int, QByteArray> roles;
        roles[TableDataRole] = "tabledata";
        roles[HeadingRole] = "heading";
        return roles;
    }

    void addPerson() {
        table.append({
          "Marc",
          "Fonz",
          "25",
        });
        int idx = table.size() - 1;
        emit dataChanged(index(idx), index(idx));
    }

private:
    QVector<QVector<QString>> table;
}; 

main.qml

import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12

import Qt.labs.settings 1.0
import Qt.labs.platform 1.0 as Platform

import QtQuick.Controls.Material 2.12
import TableModel 0.1

ApplicationWindow {
    id: window
    visible: true
    width: 1040
    height: 480

    signal on_ButtonUpdateListModel_click()

    ColumnLayout{
        spacing: 2
        anchors.fill: parent
        Button {
            text: qsTr("Update List Model")
            onClicked: on_ButtonUpdateListModel_click()
        }

        TableModel {
            id: myModel
        }
        TableView {
            width: 400
            height: 200
            columnSpacing: 1
            rowSpacing: 1
            clip: true

            ScrollIndicator.horizontal: ScrollIndicator { }
            ScrollIndicator.vertical: ScrollIndicator { }

            model: myModel


            delegate: Rectangle {
                implicitWidth: 100
                implicitHeight: 20
                border.color: "black"
                border.width: 2
                color: (heading==true) ? 'teal':"green"

                TableView.onPooled: console.log(tabledata + " pooled")
                TableView.onReused: console.log(tabledata + " resused")

                Text {
                    text: tabledata
                    font.pointSize: 10
                    anchors.centerIn: parent
                }
            }
        }

    }
}

1 Answers

2
eyllanesc On Best Solutions

You have the following errors:

  • In addPerson you are adding a row so you should not use dataChanged since this signal indicates that something that already exists was modified, instead you should use beginInsertRows() and endInsertRows().

  • TableModel created in MainWindow is different from TableModel created in QML.

  • You do not need to create a QQmlApplicationEngine in main.cpp since you only have to use the one in MainWindow.

  • You must separate the business logic from the view so it is considered bad practice to export objects from QML to C++.

Considering the above, the solution is:

  • Make the addPerson method a Q_INVOKABLE so that it is accessible in QML.

  • Export the TableModel at startup to QML using setContextProperty so it is not necessary to register TableModel.

main.cpp

#include "MainWindow.h"

#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);
    MainWindow mainwindow;
    return app.exec();
}

TableModel.h

#pragma once

#include <QAbstractTableModel>
#include <QObject>

class TableModel : public QAbstractTableModel {
    Q_OBJECT
    enum TableRoles { TableDataRole = Qt::UserRole + 1, HeadingRole };
public:
    explicit TableModel(QObject *parent = nullptr) : QAbstractTableModel(parent) {
        table.append({
                         "First Name",
                         "Last Name",
                         "Age",
                     });
    }
    int rowCount(const QModelIndex & = QModelIndex()) const override {
        return table.size();
    }

    int columnCount(const QModelIndex & = QModelIndex()) const override {
        return table.at(0).size();
    }

    QVariant data(const QModelIndex &index, int role) const override {
        switch (role) {
        case TableDataRole: {
            return table.at(index.row()).at(index.column());
        }
        case HeadingRole: {
            return index.row() == 0;
        }
        default: break;
        }
        return QVariant();
    }
    QHash<int, QByteArray> roleNames() const override {
        QHash<int, QByteArray> roles;
        roles[TableDataRole] = "tabledata";
        roles[HeadingRole] = "heading";
        return roles;
    }

    Q_INVOKABLE void addPerson() {
        beginInsertRows(QModelIndex(), rowCount(), rowCount());
        table.append({
                         "Marc",
                         "Fonz",
                         "25",
                     });
        endInsertRows();
    }
private:
    QVector<QVector<QString>> table;
};

MainWindow.h

#pragma once

#include <QQmlApplicationEngine>
#include <QQmlContext>

#include "TableModel.h"

class MainWindow : public QObject {
    Q_OBJECT
public:
    explicit MainWindow() {
        engine_.rootContext()->setContextProperty("myModel", &model_);
        engine_.load(QUrl(QStringLiteral("qrc:/main.qml")));
    }
private:
    TableModel model_;
    QQmlApplicationEngine engine_;
};

main.qml

import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12

import Qt.labs.settings 1.0
import Qt.labs.platform 1.0 as Platform

import QtQuick.Controls.Material 2.12

ApplicationWindow {
    id: window
    visible: true
    width: 1040
    height: 480
    ColumnLayout{
        spacing: 2
        anchors.fill: parent
        Button {
            text: qsTr("Update List Model")
            onClicked: myModel.addPerson()
        }
        TableView {
            width: 400
            height: 200
            columnSpacing: 1
            rowSpacing: 1
            clip: true
            ScrollIndicator.horizontal: ScrollIndicator { }
            ScrollIndicator.vertical: ScrollIndicator { }
            model: myModel
            delegate: Rectangle {
                implicitWidth: 100
                implicitHeight: 20
                border.color: "black"
                border.width: 2
                color: heading ? 'teal':"green"
                TableView.onPooled: console.log(tabledata + " pooled")
                TableView.onReused: console.log(tabledata + " resused")

                Text {
                    text: tabledata
                    font.pointSize: 10
                    anchors.centerIn: parent
                }
            }
        }
    }
}

Output:

enter image description here


The complete solution can be found here