Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

QtQuick TableView not working with C++-QAbstractTableModel

Tags:

qt

tableview

qml

I am trying to get the Qt model/view-architecture working with a QML-View, but for whatever reason it is only partially working.

What works:

  • rowCount
  • data
  • roleNames

Not working:

  • columnCount (the method is called, but seems to have no effect, as long is it is > 0)
  • headerData (is this actually supposed to set the column headers? All examples set the headers in QML)
  • flags
  • setData

What I am trying to do (for some weeks already), is to create a simple ApplicationView with a TableView and a C++-model, which is editable by the view.

Right now only a whole row is selectable, not a single cell. The table data doesn't seem to be editable at all. Can anyone give me a hint?

main.qml

import QtQuick 2.3
import QtQuick.Controls 1.2

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

    TableView {
        model: theModel
        TableViewColumn {
            role: "nameRole"
            width: 75
        }
        TableViewColumn {
            role: "ageRole"
            width: 50
        }
    }
}

ModelItem.hpp

#ifndef MODELITEM
#define MODELITEM

#include <QString>

struct ModelItem {
    ModelItem(QString name_, int age_)
        : name(name_), age(age_) {}
    QString name;
    int age;
};

#endif // MODELITEM

TableModel.hpp

#ifndef TABLEMODEL_HPP
#define TABLEMODEL_HPP

#include <QAbstractTableModel>
#include "ModelItem.hpp"

class TableModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    TableModel(QObject *parent = 0);

    //works
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
    int rowCount(const QModelIndex &parent) const;
    int columnCount(const QModelIndex &parent) const;

    //does not work
    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;

    QHash<int, QByteArray> roleNames() const;
    enum Roles {
        NameRole = Qt::UserRole + 1,
        AgeRole
    };

    Qt::ItemFlags flags(const QModelIndex &index) const;
    bool setData(const QModelIndex &index, const QVariant &value, int role);

private:
    QList<ModelItem*> items;
};

#endif // TABLEMODEL_HPP

TableModel.cpp

#include "TableModel.hpp"

TableModel::TableModel(QObject *parent)
    : QAbstractTableModel(parent) {
    items.append(new ModelItem("Hugo",33));
    items.append(new ModelItem("Egon",34));
    items.append(new ModelItem("Balder",66));
    qDebug("TableModel initialisiert");
}


int TableModel::columnCount(const QModelIndex &parent) const {
    Q_UNUSED(parent);
    qDebug("columnCount");
    return 2;
}

int TableModel::rowCount(const QModelIndex &parent) const {
    Q_UNUSED(parent);
    qDebug("rowCount");
    return items.count();
}

QVariant TableModel::data(const QModelIndex &index, int role) const {
    qDebug("data");
    switch (role) {
        case NameRole: return items[index.row()]->name;
        case AgeRole: return items[index.row()]->age;
    }
}

QVariant TableModel::headerData(int section, Qt::Orientation orientation, int role) const {
    qDebug("headerData");
    switch (role) {
        case NameRole: return "1";
        case AgeRole: return "2";
    };
    return QVariant();
}

QHash<int, QByteArray> TableModel::roleNames() const {
    QHash<int, QByteArray> roles;
    roles[NameRole] = "nameRole";
    roles[AgeRole] = "ageRole";
    qDebug("roleNames initialised");

    return roles;
}

Qt::ItemFlags TableModel::flags(const QModelIndex &index) const {
    qDebug("--flags called--");
    return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
}

bool TableModel::setData(const QModelIndex &index, const QVariant &value, int role) {
    qDebug("setData called");
    switch (role) {
        case NameRole: items[index.row()]->name = value.toString();
        case AgeRole: items[index.row()]->age = value.toInt();
    }
    emit dataChanged(index, index);
    return true;
}

main.cpp

#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>

#include "TableModel.hpp"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QQmlApplicationEngine engine;

    TableModel model;
    engine.rootContext()->setContextProperty("theModel", &model);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    return app.exec();
}
like image 930
Marc Q Avatar asked Dec 26 '14 09:12

Marc Q


1 Answers

columnCount and rowCount called in QAbstractItemModel::index method, which is called by TableView before data method.

QModelIndex QAbstractTableModel::index(int row, int column, const QModelIndex &parent) const
{
    return hasIndex(row, column, parent) ? createIndex(row, column, 0) : QModelIndex();
}

bool QAbstractItemModel::hasIndex(int row, int column, const QModelIndex &parent) const
{
    if (row < 0 || column < 0)
        return false;
    return row < rowCount(parent) && column < columnCount(parent);
}

columnCount has no effect, as long is it is > 0, because TableView called the index method with column always equal to 0.

headerData and flags did not affect the QML TableView. You can only set the headers on the QML side. To create an editable TableView, you should implement your custom itemDelegate

main.qml

ApplicationWindow {
    visible: true
    id: root
    Component {
        id: editableDelegate
        Item {
            Text {
                width: parent.width
                anchors.margins: 4
                anchors.left: parent.left
                anchors.verticalCenter: parent.verticalCenter
                elide: styleData.elideMode
                text: styleData.value !== undefined ? styleData.value : ""
                color: styleData.textColor
                visible: !styleData.selected
            }
            Loader {
                id: loaderEditor
                anchors.fill: parent
                anchors.margins: 4
                Connections {
                    target: loaderEditor.item
                    onEditingFinished: {
                        theModel.setData(styleData.row, styleData.column, loaderEditor.item.text)
                    }
                }
                sourceComponent: styleData.selected ? editor : null
                Component {
                    id: editor
                    TextInput {
                        id: textinput
                        color: styleData.textColor
                        text: styleData.value
                        MouseArea {
                            id: mouseArea
                            anchors.fill: parent
                            hoverEnabled: true
                            onClicked: textinput.forceActiveFocus()
                        }
                    }
                }
            }
        }
    }
    TableView {
        id: table
        anchors.fill: parent
        model: theModel
        itemDelegate: editableDelegate;
        TableViewColumn {
            role: "nameRole"
            width: 75
            title: "name"
        }
        TableViewColumn {
            role: "ageRole"
            width: 50
            title: "age"
        }
    }
}

To apply the changes to your model, you should implement the setData method, like this:

TableModel.h

bool setData(const QModelIndex &index, const QVariant &value, int role) {
    switch (role) {
        case NameRole: items[index.row()]->name = value.toString(); break;
        case AgeRole: items[index.row()]->age = value.toInt(); break;
    }
    emit dataChanged(index, index);
    return true;
}

Q_INVOKABLE bool setData(int row, int column, const QVariant value)
{
    int role = Qt::UserRole + 1 + column;
    return setData(index(row,0), value, role);
}
like image 87
Meefte Avatar answered Sep 22 '22 00:09

Meefte