1
votes

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)

2
possible duplicate of "stackoverflow.com/questions/23856114/…"PRIME

2 Answers

1
votes

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.

2
votes

You can directly set a value to model.two and that will automatically call setData with the correct role and index:

import QtQuick 2.10
import QtQuick.Controls 2.0 as QQC2
import QtQuick.Controls 1.4 as QQC1
import QtQuick.Layouts 1.3

QQC2.ApplicationWindow {
    visible: true
    width: 640
    height: 480

    ColumnLayout {
        anchors.fill: parent
        Repeater {
            model: 2
            QQC1.TableView {
                Layout.fillWidth: true
                Layout.fillHeight: true
                QQC1.TableViewColumn {
                    title: "Parameter Name"
                    role: "one"
                }
                QQC1.TableViewColumn {
                    title: "Value"
                    role: "two"
                    delegate: Loader {
                        property var modelTwo: model.two
                        sourceComponent: typeof(model.two) ==='boolean' ? checkBoxDelegate : stringDelegate
                        function updateValue(value) {
                            model.two = value;
                        }
                    }
                }
                model: myModel
            }
        }
    }

    Component {
        id: checkBoxDelegate
        QQC1.CheckBox {
            text: modelTwo
            checked: modelTwo
            onCheckedChanged: {
                updateValue(checked);
                checked = Qt.binding(function () { return modelTwo; }); // this is needed only in QQC1 to reenable the binding
            }
        }
    }

    Component {
        id: stringDelegate
        TextEdit {
            text: modelTwo
            onTextChanged: updateValue(text)
        }
    }
}

And if that's still too verbose and not enough declarative for you (it is for me), you can use something like the following, where most of the logic is in the Loader and the specifics delegates just inform what is the property where the value should be set and updated from :

delegate: Loader {
    id: loader
    sourceComponent: typeof(model.two) ==='boolean' ? checkBoxDelegate : stringDelegate
    Binding {
        target: loader.item
        property: "editProperty"
        value: model.two
    }
    Connections {
        target: loader.item
        onEditPropertyChanged: model.two = loader.item.editProperty
    }
}

//...

Component {
    id: checkBoxDelegate
    QQC1.CheckBox {
        id: checkbox
        property alias editProperty: checkbox.checked
        text: checked
    }
}

Component {
    id: stringDelegate
    TextEdit {
        id: textEdit
        property alias editProperty: textEdit.finishedText // you can even use a custom property
        property string finishedText
        text: finishedText
        onEditingFinished: finishedText = text
    }
}