【发布时间】:2021-03-27 09:41:30
【问题描述】:
我在 Manjaro 上使用 Qt 5.15.0。
我有一个在 qml 中使用 TableView 创建的表。
我可以通过右键单击 Id 字段来删除一行,如下所示:
删除等工作,但我在 qml 中收到这些错误:
删除行的方法在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())
}
错误指向main.qml中id的delegate行
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)
}
}
}
行的删除是从 C++ 在派生自 questionsproxmodel.h 中的 QIdentityProxyModel 的类中实现的:
bool QuestionsProxyModel::removeEntry(int row)
{
return removeRows(row, 1);
}
此模型采用派生自 QSqlTableModel 的类 QuestionSqlTableModel 作为源模型
删除行在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;
}
据我了解,模型的 countOfRows 仅在调用 select() 后才会更新,因此我假设在 QSqlTableModel::removeRows 和 select 之间,TableView 会再读取一次不存在的行并在 QML 中导致这些错误。如何预防?
完整源代码试用:
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;
};
问题sqltablemodel.cpp
#include "questionsqltablemodel.h"
#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 "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
}
}
.pro:
QT += quick
QT += quickcontrols2
QT += sql
CONFIG += c++17
DEFINES += QT_DEPRECATED_WARNINGS
HEADERS += \
questionsproxymodel.h \
questionsqltablemodel.h
SOURCES += \
main.cpp \
questionsproxymodel.cpp \
questionsqltablemodel.cpp
RESOURCES += qml.qrc
# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
编辑:
答案解决了上述错误。但是我检测到代码的另一个问题。
如果我删除第 1 行,然后删除第 2 行,我的输出如下所示:
这个绑定循环错误指向Dialog in QuestionsIDDelegate:
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
}
【问题讨论】:
-
我将其缩短为最小示例。由于 sql 背景,它仍然有点笨重
-
您使用的角色名称为
id。我不确定这是否真的会导致您的应用出现问题,但至少,我认为它不安全,因为它是内置的 QML 属性。我很想知道您是否只需更改名称就能获得更好的结果? -
这是一个很好的观点。我想我会重命名该列。但是在我的实际应用程序中,我有更多未命名为 id 的列发生相同的分配错误。