C++ – Using a QAbstractTableModel with a Qml TableView only displays the 1st column

cmodelqmlqt

I'm trying to use a (class derived from) QAbstractTableModel with a Qml TableView;

However, only the 1st column is displayed.

The reason is QVariant MyModel::data(const QModelIndex &index, int role) isn't called for non-zero columns, but I don't understand why.

A QTableView works fine however.

I've made a separate, simple project that reproduce my issue:

MyModel.h :

#ifndef MYMODEL_H
#define MYMODEL_H

#include <QObject>
#include <QAbstractTableModel>
#include <QList>
#include <QString>
#include <QDebug>

struct SimpleData
{
    QString m_one;
    qint32 m_two;
    qreal m_three;
};

class MyModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    explicit MyModel();//MyData *the_data);
    int rowCount(const QModelIndex & parent = QModelIndex()) const Q_DECL_OVERRIDE;
    int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;

    QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
signals:

public slots:
    void theDataChanged();

private:
   QList<SimpleData> m_the_data;

};

#endif // MYMODEL_H

mymodel.cpp:
#include "mymodel.h"

MyModel::MyModel() : QAbstractTableModel(0)
{
    m_the_data << SimpleData{"Alpha", 10, 100.0}
               << SimpleData{"Beta", 20, 200.0}
               << SimpleData{"Gamma", 30, 300.0}
               << SimpleData{"Delta", 40, 400.0};
}

int MyModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return m_the_data.size();
}

int MyModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return 3;
}

QVariant MyModel::data(const QModelIndex &index, int role) const
{
    // Check DisplayRole
    if(role != Qt::DisplayRole)
    {
        return QVariant();
    }

    // Check boudaries
    if(index.column() < 0 ||
            columnCount() <= index.column() ||
            index.row() < 0 ||
            rowCount() <= index.row())
    {
        qDebug() << "Warning: " << index.row() << ", " << index.column();
        return QVariant();
    }

    // Nominal case
     qDebug() << "MyModel::data: " << index.column() << "; " << index.row();
    switch(index.column())
    {
        case 0:
            return m_the_data[index.row()].m_one;
        case 1:
            return  m_the_data[index.row()].m_two;
        case 2:
            return  m_the_data[index.row()].m_three;
        default:
            qDebug() << "Not supposed to happen";
            return QVariant();
    }
}

QHash<int, QByteArray> MyModel::roleNames() const
{
    QHash<int, QByteArray> roles;
    roles[0] = "one";
    roles[1] = "two";
    roles[2] = "three";
    return roles;

}

void MyModel::theDataChanged()
{
    //TODO
}

main.qml:

import QtQuick 2.1
import QtQuick.Controls 1.0

ApplicationWindow {
    title: qsTr("Hello World")
    width: 640
    height: 480

    menuBar: MenuBar {
        Menu {
            title: qsTr("File")
            MenuItem {
                text: qsTr("Exit")
                onTriggered: Qt.quit();
            }
        }
    }
TableView {

    anchors.fill: parent

    TableViewColumn {title: "1"; role: "one"; width: 70 }
    TableViewColumn {title: "2"; role: "two"; width: 70   }
    TableViewColumn {title: "3"; role: "three"; width: 70 }

    model: theModel

}

}

main.cpp:

#include <QtQml>
#include <QQmlApplicationEngine>
#include <QApplication>
#include <QQuickWindow>
#include <QTableView>

#include "mymodel.h"

int main(int argc, char *argv[])
{
    // Application :
    QApplication app(argc, argv);

    // Data stuff :
    //MyData data(&app);
    MyModel model;



 // UI :
    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("theModel", &model);

    engine.load(QUrl("qrc:/qml/main.qml"));
    QList<QObject*> temp = engine.rootObjects();
    QObject *topLevel = temp.value(0);
    QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel);
    if ( !window ) {
        qWarning("Error: Your root item has to be a Window.");
        return -1;


 }

    // Display the main.qml, which show the model:
    window->show();

    // Same, using a QTableView:
    QTableView view;;
    view.setModel(&model);
    view.show();

    return app.exec();
}

qDebug output about the TableView (row, then column):

MyModel::data:  0 ;  0 
MyModel::data:  0 ;  0 
MyModel::data:  0 ;  1 
MyModel::data:  0 ;  1 
MyModel::data:  0 ;  2 
MyModel::data:  0 ;  2 
MyModel::data:  0 ;  3 
MyModel::data:  0 ;  3 

qDebug output about the QTableVierw:

MyModel::data:  0 ;  0 
MyModel::data:  0 ;  0 
MyModel::data:  0 ;  0 
MyModel::data:  1 ;  0 
MyModel::data:  2 ;  0 
MyModel::data:  0 ;  1 
MyModel::data:  1 ;  1 
MyModel::data:  2 ;  1 
MyModel::data:  0 ;  2 
MyModel::data:  1 ;  2 
MyModel::data:  2 ;  2 
MyModel::data:  0 ;  3 
MyModel::data:  1 ;  3 
MyModel::data:  2 ;  3 

Notes / stuff I tried:

  • In the code I gave, I could use a QQmlListProperty; however my actual code is more complex

    • the data is actually queried,
    • I don't have a SimpleData class
    • I'm basically using the QAbstractTableModel as a proxy.
      As a consequence, I can't switch to a QQmlListProperty (or equivalent)
  • there was a project to check models, ModelTest, however it does not work with Qt 5.2.

  • the type of data (QString, qreal) isn't an issue: swapping still display only the 1st column.

Relevant links:

http://qt-project.org/doc/qt-5/QAbstractTableModel.html

http://qt-project.org/doc/qt-5/qtquick-modelviewsdata-cppmodels.html#qabstractitemmodel

Thanks in advance!

Specs: Windows 7, Qt 5.2, Mingw 4.8, Qt Creator 3.0

Best Answer

The TableViewColumn API suggests that the data from the column is retrieved via roles instead of columns, i.e. "one", "two", and "three", while the column passed will be always 0. You return QVariant() for everything but Qt::DisplayRole. Qt::DisplayRole is 0, converted to int. In rolenames, you set the name for 0 to "one", so that's why you happen to see something for "one" (DisplayRole).

So to fix this, you must return something for one, two, and three. I'd suggest to define a custom roles enum in the header:

//In class MyModel:
enum Role {
    OneRole=Qt::UserRole,
    TwoRole,
    ThreeRole
};

Define roleNames like this:

QHash<int, QByteArray> MyModel::roleNames() const
{
    QHash<int, QByteArray> roles;
    roles[OneRole] = "one";
    roles[TwoRole] = "two";
    roles[ThreeRole] = "three";
    return roles;
}

Note that I started with Qt::UserRole instead of 0. That avoids collision with predefined roles such as Qt::DisplayRole.

Then return something for One, Two and Three, in data():

...
switch(role)
{
    case OneRole:
        return m_the_data[index.row()].m_one;
    case TwoRole:
        return m_the_data[index.row()].m_two;
    case ThreeRole:
        return m_the_data[index.row()].m_three;
}
...

Now you should see the data.

It seems that the TableView/TableViewColumn from QtQuickControls does a somewhat unfortunate and confusing mix of role and columns: While the naming let's one think of model columns (but they actually refer to the view's columns here), one can retrieve data only via different roles, with the column being fixed to 0. (To me there should be another optional property "column" in TableViewColumn.) It's a bit of a clash between the C++ QAbstractItemModel/QTableView way of things, where multiple columns are a natural thing to do, and QtQuick views, which all use only roles to refer to data, often not supporting multiple columns at all (ListView, GridView).

Related Topic