How to delete row from TableView properly
-
I have a table I created with TableView in qml.
I can delete a row with right click on the Id field like this:
The deletion etc works but I get these errors in qml:
The method for delete rows looks like this in
main.qml
:function deleteRowFromDatabase(row) { console.log("before" + model.countOfRows()) if (!model.removeEntry(row)) { console.log(qsTr("remove row %1 failed").arg(row)) } model = QuestionsProxyModel console.log("after" + model.countOfRows()) }
The error point to the delegate row of id in
main.qml
DelegateChoice { column: 0 delegate: QuestionIdDelegate { id: questionIdDelegate width: tableView.columnWidthProvider(column) text: model.id /// this is undefined row: model.row Component.onCompleted: { questionIdDelegate.markForDelete.connect( tableView.deleteRowFromDatabase) } } }
Removing of the rows is implemented from C++ In a class derrived from
QIdentityProxyModel
inquestionsproxmodel.h
:bool QuestionsProxyModel::removeEntry(int row) { return removeRows(row, 1); }
This model takes a class
QuestionSqlTableModel
derrived fromQSqlTableModel
as a source modelThe remove rows is implemented like this in
questionssqltablemodel.qml
:bool QuestionSqlTableModel::removeRows(int row, int count, const QModelIndex &parent) { auto result = QSqlTableModel::removeRows(row, count, parent); if (result) { select(); // row is not deleted from sql database until select is called } return result; }
From my understanding the countOfRows of the model gets updated only after the
select()
is called so I assume betweenQSqlTableModel::removeRows
andselect
the TableView reads one more time with the non existing row and causes these errors in QML. How can that be prevented?Full Source code to try it out:
main.cpp:
#include <QDebug> #include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQuickStyle> #include <QFile> #include <QSqlDatabase> #include <QSqlQuery> #include <QSqlError> #include "questionsproxymodel.h" #include "questionsqltablemodel.h" int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); QUrl dbUrl{"file:///home/sandro/Desktop/test.db"}; auto exists = QFile::exists(dbUrl.toLocalFile()); auto db = QSqlDatabase::addDatabase("QSQLITE", "DBConnection"); db.setDatabaseName(dbUrl.toLocalFile()); db.open(); if (!exists) { const QString questionTableName = "questions"; QSqlQuery query{db}; query.exec("CREATE TABLE " + questionTableName + " (" "id INTEGER PRIMARY KEY AUTOINCREMENT)"); } QScopedPointer<QuestionSqlTableModel> questionSqlTableModel( new QuestionSqlTableModel(nullptr, db)); QScopedPointer<QuestionsProxyModel> questionsProxyModel{ new QuestionsProxyModel}; questionsProxyModel->setSourceModel(questionSqlTableModel.get()); if (!exists) { for (int i = 0; i < 10; ++i) { questionsProxyModel->addNewEntry(); } } QQmlApplicationEngine engine; qmlRegisterSingletonInstance<QuestionsProxyModel>( "QuestionsProxyModels", 1, 0, "QuestionsProxyModel", questionsProxyModel.get()); const QUrl url(QStringLiteral("qrc:/qml/main.qml")); engine.load(url); return app.exec(); }
questionssqltablemodel.h
#include <QSqlTableModel> class QuestionSqlTableModel : public QSqlTableModel { Q_OBJECT public: explicit QuestionSqlTableModel(QObject *parent = nullptr, const QSqlDatabase &db = QSqlDatabase()); bool removeRows(int row, int count, const QModelIndex &parent) override; };
questionssqltablemodel.cpp
#include <QBuffer> #include <QDebug> #include <QPixmap> #include <QSqlError> #include <QSqlField> #include <QSqlRecord> #include <QSqlRelationalDelegate> QuestionSqlTableModel::QuestionSqlTableModel(QObject *parent, const QSqlDatabase &db) : QSqlTableModel{parent, db} { setTable("questions"); setSort(0, Qt::AscendingOrder); if (!select()) { qDebug() << "QuestionSqlTableModel: Select table questions failed"; } setEditStrategy(EditStrategy::OnFieldChange); } bool QuestionSqlTableModel::removeRows(int row, int count, const QModelIndex &parent) { auto result = QSqlTableModel::removeRows(row, count, parent); if (result) { select(); // row is not deleted from sql database until select is called } return result; }
questionsproxymodel.h:
#include <QIdentityProxyModel> #include <QObject> class QuestionsProxyModel : public QIdentityProxyModel { Q_OBJECT enum questionRoles { idRole = Qt::UserRole + 1, }; public: QuestionsProxyModel(QObject *parent = nullptr); QHash<int, QByteArray> roleNames() const override; Q_INVOKABLE QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; bool addNewEntry(); Q_INVOKABLE bool removeEntry(int row); Q_INVOKABLE int countOfRows() const; private: QModelIndex mapIndex(const QModelIndex &source, int role) const; };
questionsproxymodel.h:
#include <QBuffer> #include <QDebug> #include <QPixmap> #include <QByteArray> #include <QSqlError> #include <QSqlTableModel> QuestionsProxyModel::QuestionsProxyModel(QObject *parent) : QIdentityProxyModel(parent) { } QHash<int, QByteArray> QuestionsProxyModel::roleNames() const { QHash<int, QByteArray> roles; roles[idRole] = "id"; return roles; } QVariant QuestionsProxyModel::data(const QModelIndex &index, int role) const { QModelIndex newIndex = mapIndex(index, role); if (role == idRole) { return QIdentityProxyModel::data(newIndex, Qt::DisplayRole); } return QIdentityProxyModel::data(newIndex, role); } bool QuestionsProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) { QModelIndex newIndex = mapIndex(index, role); if (role == idRole) { return QIdentityProxyModel::setData(newIndex, value, Qt::EditRole); } return QIdentityProxyModel::setData(newIndex, value, role); } bool QuestionsProxyModel::addNewEntry() { auto newRow = rowCount(); if (!insertRows(newRow, 1)) { return false; } if (!setData(createIndex(newRow, 0), newRow + 1)) { removeRows(newRow, 1); return false; } auto sqlModel = qobject_cast<QSqlTableModel *>(sourceModel()); return sqlModel->submit(); } bool QuestionsProxyModel::removeEntry(int row) { return removeRows(row, 1); } int QuestionsProxyModel::countOfRows() const { return rowCount(); } QModelIndex QuestionsProxyModel::mapIndex(const QModelIndex &source, int role) const { switch (role) { case idRole: return createIndex(source.row(), 0); } return source; }
main.qml
import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Window 2.15 import Qt.labs.qmlmodels 1.0 import QtQuick.Controls.Material 2.15 import QuestionsProxyModels 1.0 ApplicationWindow { id: root visible: true width: 1460 height: 800 TableView { id: tableView width: parent.width anchors.fill: parent boundsBehavior: Flickable.StopAtBounds reuseItems: true clip: true property var columnWidths: [60] columnWidthProvider: function (column) { return columnWidths[column] } model: QuestionsProxyModel delegate: DelegateChooser { id: chooser DelegateChoice { column: 0 delegate: QuestionIdDelegate { id: questionIdDelegate width: tableView.columnWidthProvider(column) text: model.id row: model.row Component.onCompleted: { questionIdDelegate.markForDelete.connect( tableView.deleteRowFromDatabase) } } } } ScrollBar.vertical: ScrollBar {} function deleteRowFromDatabase(row) { console.log("before" + model.countOfRows()) if (!model.removeEntry(row)) { console.log(qsTr("remove row %1 failed").arg(row)) } model = QuestionsProxyModel console.log("after" + model.countOfRows()) } } }
QuestionIdDelegate.qml:
import QtQuick 2.15 import QtQuick.Controls 2.15 TextField { property int row signal markForDelete(int row) id: root implicitHeight: 100 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter readOnly: true background: Frame {} MouseArea { id: mouseArea anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: { eraseContextMenu.popup(root, 0, mouseArea.mouseY + 10) } } Menu { id: eraseContextMenu y: root.y MenuItem { text: qsTr("Delete entry") onTriggered: { eraseDialog.open() eraseContextMenu.close() } } MenuItem { text: qsTr("Cancel") onTriggered: { eraseContextMenu.close() } } } Dialog { id: eraseDialog title: qsTr("Delete database entry") modal: true focus: true contentItem: Label { id: label text: qsTr("Do you really want to erase the entry with id %1?").arg( root.text) } onAccepted: { markForDelete(root.row) } standardButtons: Dialog.Ok | Dialog.Cancel } }
-
@sandro4912
I couldn't see any call tobeginRemoveRows(...) ... endRemoveRows(...)
in your code. Refer to:
https://doc.qt.io/qt-5/qabstractitemmodel.html#beginRemoveRowsSometimes it is easier just to use the following pair instead:
beginResetModel(); ... endResetModel();
https://doc.qt.io/qt-5/qabstractitemmodel.html#beginResetModel
-
@Diracsbracket said in How to delete row from TableView properly:
endResetModel();
Were would you add that?
I added it to the function:
bool QuestionSqlTableModel::removeRows(int row, int count, const QModelIndex &parent)
but still get the errors in qml.
-
Normally you would place it like this:
bool QuestionSqlTableModel::removeRows(int row, int count, const QModelIndex &parent) { beginRemoveRows() //<--- or beginResetModel() auto result = QSqlTableModel::removeRows(row, count, parent); if (result) { select(); // row is not deleted from sql database until select is called } endRemoveRows(); //<-- or endResetModel() return result; }
-
You are right I totally forgot to add that.
Although It did not solve the assign errors. I still had to change in the delegates:
text: model.id === undefined ? "" : model.id
And I realized there is another error which still shows up:
If I delete the row 1 and then the row 2 my output looks like this:
This binding loop error points
Dialog
inQuestionsIDDelegate
:Dialog { id: eraseDialog title: qsTr("Delete database entry") modal: true focus: true contentItem: Label { id: label text: qsTr("Do you really want to erase the entry with id %1?").arg( root.text) } onAccepted: { markForDelete(root.row) } standardButtons: Dialog.Ok | Dialog.Cancel }
-
@sandro4912 said in How to delete row from TableView properly:
This binding loop error points Dialog in QuestionsIDDelegate:
That is probably a bug in your Qt version. I'm tested it in Qt 5.15.2, and I get no binding loop.
As a workaround, try setting theheight
of theLabel
explicitly.@sandro4912 said in How to delete row from TableView properly:
Assuming that
id
is always defined in your model, this shouldn't be necessary (imagine having to do this check for every field of your model you use in the delegate).Maybe try with
begin/endResetModel()
instead ofbegin/endRemoveRows()
? -
@Diracsbracket said in How to delete row from TableView properly:
Qt 5.15
Did you try to reproduce the binding loop to delete several elements? If I delete the first it did not happen.
Im Also on Qt 5.15
-
I also asked this question here:
Even If I used signals and slots to invoke the methods not directly from javascript I still get these errors: