10

I have been trying to use a QML TableView to display a QAbstractTableModel. The missing part of the equation seems to be that it is not possible to have a variable number of columns in the TableView, despite overriding QAbstractItemModel::roleNames which should tell Qt the number and name of my columns. I tried testing this out using only QML:

import QtQuick 2.0
import QtQuick.Controls 1.1

Rectangle {
    anchors.fill: parent
    property real showImage: 1.0
    width: 500
    TableView {
        id: myTable
        model: myModel
        //        TableViewColumn {
        //            role: "title"; title: "Name"; width: 200
        //        }
    }

    ListModel {
        id: myModel
        ListElement {
            title: "item one"
        }
        ListElement {
            title: "item two"
        }
    }
}

When run this doesn't show anything despite the TableView's mode containing ListElements with roles defined in them.

However if the above code is uncommented and a TableViewColumn is defined then the column will display data for that role as expected but the table will still not display any other roles. Obviously that will only work for a statically defined number of columns and not my case where the number of columns is not known until run time.

The example given is basically the same as my real life example except that my model is defined in C++.

It seems as if this may have already been asked here but it did not gain any response.

EDIT: I had tried calling a javascript function:

function addColumnToTable(roleName) {
    var columnString = 'import QtQuick 2.3; import QtQuick.Controls 1.2; TableViewColumn {role: "'
            + roleName + '"; title: "' + roleName + '"; width: 40}';
    var column = Qt.createQmlObject(
                columnString
                , myTable
                , "dynamicSnippet1")
    myTable.addColumn(column);
}

From C++:

QVariant roleName = "name";
QObject *root = view->rootObject();
QMetaObject::invokeMethod(root, "addColumnToTable", Q_ARG(QVariant, roleName));

This at least allowed me to dynamically add columns from C++ although not from within the model/view architecture. Yoann's solution is far and away better than this though.

Community
  • 1
  • 1
sjdowling
  • 2,994
  • 2
  • 21
  • 31

3 Answers3

15

You could create dynamically as many TableViewColumn as you need, using the resources property of your TableView.

You will have to add a method in your custom model class which will give you the roleNames you want to display.

QML:

Component
{
    id: columnComponent
    TableViewColumn{width: 100 }
}

TableView {
    id: view
    anchors.fill: parent
    resources:
    {
        var roleList = myModel.customRoleNames
        var temp = []
        for(var i=0; i<roleList.length; i++)
        {
            var role  = roleList[i]
            temp.push(columnComponent.createObject(view, { "role": role, "title": role}))
        }
        return temp
    }

    model: myModel

MyModel.h:

class MyModel: public QAbstractListModel
{
    Q_OBJECT
    Q_PROPERTY(QStringList userRoleNames READ userRoleNames CONSTANT)

public:
    explicit MyModel(QObject *parent = 0);

    enum MyModelRoles {
        UserRole1 = Qt::UserRole + 1,
        UserRole2,
        ...
    };

    QStringList userRoleNames();
    int rowCount(const QModelIndex & parent = QModelIndex()) const;
    QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
    ...

private:
    QHash<int, QByteArray> roleNames() const;
    ...

};

MyModel.cpp:

...
...

QHash<int, QByteArray> MyModel::roleNames() const {
    QHash<int, QByteArray> roles = QAbstractListModel::roleNames ();
    roles[UserRole1] = "whatever";
    roles[UserRole2] = "youwant";
    return roles;
}

QStringList MyModel::userRoleNames() // Return ordered List of user-defined roles
{
    QMap<int, QString> res;
    QHashIterator<int, QByteArray> i(roleNames());
    while (i.hasNext()) {
        i.next();
        if(i.key() > Qt::UserRole)
            res[i.key()] = i.value();
    }
    return res.values();
}

...
...
  • 2
    Excellent answer! I had to dig through the documentation a bit just to find the `resources` property and even from that I would not have known it could be used in this way. – sjdowling Dec 02 '14 at 14:35
3

The solution with resources does not work for me (Qt 5.6). The TableView is not updated.

This works for me:

Component{
    id: columnComponent
    TableViewColumn{width: 30 }
}

TableView {
    id: tableView
    model: listModel
    property var titles: somethingDynamic
    property var curTitles: {
        var t=[]
        for(var i=0;i<columnCount;i++){
            t.push(getColumn(i).title)
        }
        return t
    }
    onTitlesChanged:{
        for(var i=0;i<titles.length;i++){
            if(curTitles.indexOf(titles[i])==-1){
                var column = addColumn(columnComponent) 
                column.title=titles[i]
                column.role=titles[i]                   
            }
        }
        for(var i=curTitles.length-1;i>=0;i--){
            if(titles.indexOf(curTitles[i])==-1){
                removeColumn(i)
            }
        }
    }
}

}

It also correctly updates drag-drop-reordered columns.

palfi
  • 288
  • 2
  • 6
0

Another way to do it with an Instantiator:

TableView{
  id: view
  model: tableViewModel
  Instantiator{
    model: someStringList
    onObjectAdded: view.addColumn(object)
    onObjectRemoved: view.removeColumn(object)
    delegate: TableViewColumn{
      width: 100
      title: modelData
      role: modelData
    }
  }
}

This code (and the one from @palfi) cause some warning in the console: For each column created there is:

qml: TableView::insertColumn(): you cannot add a column to multiple views

And then when column are removed it produce this warning:

 file:///usr/lib/qt/qml/QtQuick/Controls/Private/BasicTableView.qml:297: Error: Invalid attempt to destroy() an indestructible object

If someone has a better solution, please post it!

GuillaumeF93
  • 111
  • 1
  • 4