Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Can I populate a TableView's rows with a C++ QVariantList?



  • I'm trying to make a recyclable TableView where each cell in a column can be editable based on the value (like a C# DataGridView).
    I've started this off by trying to populate the table's rows using a QVariantList that is retrieved from a C++ class. However, I get a few error...

    Is there anyway to accomplish this without having to define a QAbstractTableModel in C++? My hopes was that I could just define the columns via QML like shown in my code.

    Errors
    qrc:/AppTableView.qml:10:12: QML TableModel: expected row for role "display" of TableModelColumn at index 0 to be a simple object, but it's QObject* instead: QVariant(QObject*, TableValue(0x20fa91742c0))
    qrc:/AppTableView.qml:10:12: QML TableModel: expected row for role "display" of TableModelColumn at index 1 to be a simple object, but it's QObject* instead: QVariant(QObject*, TableValue(0x20fa91742c0))
    qrc:/AppTableView.qml:0: ReferenceError: display is not defined
    qrc:/AppTableView.qml:0: ReferenceError: display is not defined
    qrc:/AppTableView.qml:0: ReferenceError: display is not defined
    qrc:/AppTableView.qml:32: ReferenceError: display is not defined
    qrc:/AppTableView.qml:32: ReferenceError: display is not defined
    qrc:/AppTableView.qml:32: ReferenceError: display is not defined

    My Code

    AppTableView.qml

    import QtQuick 2.15
    import QtQuick.Controls 2.15
    import Qt.labs.qmlmodels 1.0
    
    TableView {
        id: root
        width: parent.width
        height: parent.height
    
        model: TableModel {
            TableModelColumn { display: "id" }
            TableModelColumn { display: "title" }
    
            // This does not work...
            rows: sampleController.tableValues
    
            /// This works, but is not populated by an existing collection of data...
            /*rows: [
                {
                    id: 0,
                    title: "Title0"
                },
                {
                    id: 1,
                    title: "Title1"
                }
            ]*/
        }
    
        delegate: TextArea {
            id: textField
            text: display
        }
    }
    

    main.qml

    import QtQuick 2.12
    import QtQuick.Window 2.12
    import QtQuick.Controls 2.12
    
    Window {
        width: 640
        height: 480
        visible: true
        title: qsTr("Hello World")
    
        AppTableView {
    
        }
    }
    

    main.cpp

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    
    #include "SampleController.h"
    
    int main(int argc, char *argv[])
    {
    #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    #endif
    
        QGuiApplication app(argc, argv);
    
    
        SampleController sampleController;
    
        QQmlApplicationEngine engine;
        engine.rootContext()->setContextProperty("sampleController", &sampleController);
    
        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();
    }
    

    SampleController.h

    #ifndef SAMPLECONTROLLER_H
    #define SAMPLECONTROLLER_H
    
    #include <QObject>
    #include <QVariantList>
    
    #include "SampleThread.h"
    
    class TableValue : public QObject
    {
        Q_OBJECT
    
        Q_PROPERTY(int id MEMBER m_id);
        Q_PROPERTY(QString title MEMBER m_title);
    
    public:
        explicit TableValue(
                const int id,
                const QString& title,
                QObject *parent = nullptr) :
            QObject(parent),
            m_id(id),
            m_title(title)
        {
    
        }
    
    private:
        int m_id;
        QString m_title;
    };
    
    class SampleController : public QObject
    {
        Q_OBJECT
    
        Q_PROPERTY(QVariantList tableValues READ tableValues NOTIFY tableValuesChanged);
    
    public:
        explicit SampleController(
                QObject *parent = nullptr);
    
        const QVariantList tableValues() const;
    
    signals:
    
        void tableValuesChanged(
                const QVariantList& tableValues);
    
    private:
        QList<TableValue*> m_table_values;
    };
    
    
    #endif // SAMPLECONTROLLER_H
    

    SampleController.cpp

    #include <QDebug>
    
    #include "SampleController.h"
    
    SampleController::SampleController(
            QObject *parent) :
        QObject(parent)
    {
        m_table_values.append(new TableValue(0, "Title0", this));
        m_table_values.append(new TableValue(1, "Title1", this));
        m_table_values.append(new TableValue(2, "Title2", this));
    }
    
    const QVariantList SampleController::tableValues() const
    {
        QVariantList list;
    
        for(int i = 0; i < m_table_values.size(); i++)
        {
            list.append(QVariant::fromValue(m_table_values[i]));
        }
    
        return list;
    }
    


  • So I did some research and I didn't find any direct answer to my question... but with a little playing around I was able to produce what I wanted by changing AppTableView.qml a little.

    Specifically, the TableModelColumn's now get the display via a function... I just don't understand why it doesn't work by directly providing the property name we are looking for (shown in my original post)... or even by doing return rows[modelIndex.row].id...

    TableModelColumn {
        display: function(modelIndex) {
            return sampleController.tableValues[modelIndex.row].id
        }
    }
    

    AppTableView.qml

    TableView {
        id: root
        width: parent.width
        height: parent.height
    
        model: TableModel {
            TableModelColumn {
                display: function(modelIndex) {
                    return sampleController.tableValues[modelIndex.row].id
                }
            }
            TableModelColumn {
                display: function(modelIndex) {
                    return sampleController.tableValues[modelIndex.row].title
                }
            }
    
            // This does not work...
            rows: sampleController.tableValues
    
            // This works, but is not populated by an existing collection of data...
            /*rows: [
                {
                    id: 0,
                    title: "Title0"
                },
                {
                    id: 1,
                    title: "Title1"
                }
            ]*/
        }
    
        delegate: TextArea {
            id: textField
            text: display
        }
    }
    


  • Anyone running into the same issue, aside from my solution above I could not find any other working solutions. That being said, even my solution above has issues...

    For example, if I were to add a "Add Row" button that inserts a TableValue object into m_table_values. This addition is not reflected to the model view (and according to the documentation this isn't supported). However, if you are okay with just editing existing entries... this method works great (they update).

    That being said, I ended up just making a basic QAbstractTableModel class and then made several others that inherit from my BasicTableModel... This produced what I wanted, but it's just very disappointing that this can't be accomplished via QML directly...


Log in to reply