Search code examples
c++qtqmlqt5qstandarditemmodel

Edit QStandardItemModel via TableView with Custom Delegate


I have a QStandardItemModel which I display via a QML Table view.

Here is the model:

class mystandardmodel: public QStandardItemModel
{

public:
    mystandardmodel();
    enum Role {
         role1=Qt::UserRole,
         role2
     };

     explicit mystandardmodel(QObject * parent = 0): QStandardItemModel(parent){}
     //explicit mystandardmodel( int rows, int columns, QObject * parent = 0 )
     //    : QStandardItemModel(rows, columns, parent){}

     QHash<int, QByteArray> roleNames() const{
          QHash<int, QByteArray> roles;
          roles[role1] = "one";
          roles[role2] = "two";
          return roles;
 }
};

and this is how the model is displayed using custom delegates:

    TableView {
        id: tableView2
        x: 69
        y: 316
        width: 318
        height: 150
        TableViewColumn {
            title: "Parameter Name"
            role: "one"
        }
        TableViewColumn {
            title: "Value"
            role: "two"
            delegate: myDelegate
        }
        model: myTestModel
    }

    Component {
        id: myDelegate
        Loader {
            property var roleTwo: model.two
            sourceComponent: if(typeof(roleTwo)=='boolean') {
                                 checkBoxDelegate}
                             else { stringDelegate}
        }
    }

    Component {
        id: checkBoxDelegate
        CheckBox{text: roleTwo}
    }

    Component {
        id: stringDelegate
        TextEdit {text: roleTwo}
    }

I populated the model like this:

 mystandardmodel* mysmodel=new mystandardmodel(0);
 QStandardItem* it = new QStandardItem();
 it->setData("data1", mystandardmodel::role1);
 it->setData(true, mystandardmodel::role2);
 it->setCheckable(true);
 it->setEditable(true);
 mysmodel->appendRow(it);
 QStandardItem* it2 = new QStandardItem();
 it2->setData("data2",mystandardmodel::role1);
 it2->setData("teststring",mystandardmodel::role2);
 mysmodel->appendRow(it2);

How can I make the model editable, so that using the checkBox or editing the text is transfered back to the model?

Edit: I tried to follow the suggestion in In QML TableView when clicked edit a data (like excel) and use set model:

Component {
    id: myDelegate
    Loader {
        property var roleTwo: model.two
        property int thisIndex: model.index
        sourceComponent: if(typeof(roleTwo)=='boolean') {
                             checkBoxDelegate}
                         else { stringDelegate}
    }
}

Component {
    id: checkBoxDelegate
    CheckBox{text: roleTwo
        onCheckedChanged: {
            myTestModel.setData(0,"two",false)
            console.log('called',thisIndex)
        }
    }

}

Component {
    id: stringDelegate
    TextEdit {text: roleTwo
        onEditingFinished: {
            myTestModel.setData(thisIndex,"two",text)
           console.log('called',thisIndex)
        }
    }
}

The index is OK, but it seems that it does not have an effect (I added a second TableView with the same model, but the data there does not get updated if I edit it in the first TableView)


Solution

  • Using setData() could be an option, but it requires an integer value that indicates the role that is not accessible in QML, or rather is not elegant.

    A better option is to create a new one that is Q_INVOKABLE. As the update is given in the view it is not necessary to notify it besides causing strange events.

    to obtain the row we use the geometry and the rowAt() method of TableView.

    The following is an example:

    main.cpp

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    #include <QStandardItemModel>
    
    class MyStandardModel: public QStandardItemModel
    {
        Q_OBJECT
    public:
        enum Role {
            role1=Qt::UserRole+1,
            role2
        };
    
        using QStandardItemModel::QStandardItemModel;
    
        QHash<int, QByteArray> roleNames() const{
            QHash<int, QByteArray> roles;
            roles[role1] = "one";
            roles[role2] = "two";
            return roles;
        }
    
        Q_INVOKABLE void updateValue(int row, QVariant value, const QString &roleName){
    
            int role = roleNames().key(roleName.toUtf8());
            QStandardItem *it = item(row);
            if(it){
                blockSignals(true);
                it->setData(value, role);
                Q_ASSERT(it->data(role)==value);
                blockSignals(false);
            }
    
        }
    };
    
    int main(int argc, char *argv[])
    {
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    
        QGuiApplication app(argc, argv);
    
        MyStandardModel model;
    
        for(int i=0; i< 10; i++){
            auto item = new QStandardItem;
            item->setData(QString("data1 %1").arg(i), MyStandardModel::role1);
            if(i%2 == 0)
                item->setData(true, MyStandardModel::role2);
            else {
                item->setData(QString("data2 %1").arg(i), MyStandardModel::role2);
            }
            model.appendRow(item);
        }
        QQmlApplicationEngine engine;
        engine.rootContext()->setContextProperty("myTestModel", &model);
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
        if (engine.rootObjects().isEmpty())
            return -1;
    
        return app.exec();
    }
    
    #include "main.moc"
    

    main.qml

    import QtQuick 2.9
    import QtQuick.Window 2.2
    import QtQuick.Controls 1.4
    
    Window {
        visible: true
        width: 640
        height: 480
        title: qsTr("Hello World")
    
        TableView {
            id: tableView2
            anchors.fill: parent
            TableViewColumn {
                title: "Parameter Name"
                role: "one"
            }
            TableViewColumn {
                title: "Value"
                role: "two"
                delegate: myDelegate
            }
            model: myTestModel
        }
    
        Component {
            id: myDelegate
            Loader {
                property var roleTwo: model.two
                sourceComponent: typeof(roleTwo)=='boolean'? checkBoxDelegate: stringDelegate
            }
        }
    
        Component {
            id: checkBoxDelegate
            CheckBox{
                checked: roleTwo
                onCheckedChanged:{
                    var pos = mapToGlobal(0, 0)
                    var p = tableView2.mapFromGlobal(pos.x, pos.y)
                    var row = tableView2.rowAt(p.x, p.y)
                    if(row >= 0)
                        myTestModel.updateValue(tableView2.row, checked, "two")
                }
            }
        }
    
        Component {
            id: stringDelegate
            TextField {
                text: roleTwo
                onEditingFinished: {
                    var pos = mapToGlobal(0, 0)
                    var p = tableView2.mapFromGlobal(pos.x, pos.y)
                    var row = tableView2.rowAt(p.x, p.y)
                    if(row >= 0)
                        myTestModel.updateValue(tableView2.row, text, "two")
                }
    
            }
        }
    }
    

    The complete example can be found in the following link.