如何使用 QSqlTableModel 在可编辑的 QTableView 中将值设置为 NULL

Posted

技术标签:

【中文标题】如何使用 QSqlTableModel 在可编辑的 QTableView 中将值设置为 NULL【英文标题】:How to set value to NULL in editable QTableView with QSqlTableModel 【发布时间】:2021-04-19 09:36:11 【问题描述】:

我有一个QTableView,它显示 SQLite 数据库中表的数据子集。该表只能对可为空的数值列进行编辑。我创建了两个代表 - 一个用于只读列:

class ReadOnlyDelegate(QtWidgets.QItemDelegate):
    def editorEvent(self, *args, **kwargs):
        return False

    def createEditor(self, *args, **kwargs):
        return None

还有一个用于可编辑列:

class LabelDelegate(QtWidgets.QItemDelegate):
    def createEditor(self, parent, options, index):
        self.le = QtWidgets.QLineEdit(parent)
        return self.le

表格由自定义的QSqlTableModel 提供,我覆盖了 submitAll 方法:

class mysqlTableModel(QtSql.QSqlTableModel):
    def submitAll(self):
        for row in range(self.rowCount()):
            for col in range(self.columnCount()):
                if self.isDirty(self.index(row, col)):
                    val = self.record(row).value(col)
                    if val == '':
                        self.record(row).setNull(col)
                    else:
                        try:
                            self.record(row).setValue(col, float(val))
                        except (TypeError, ValueError):
                            display_error_msg('Can not convert to float',
                                              f'The value val could not be converted to float')
                            raise
        super().submitAll()

预期的行为是 (1) 在发送到数据库之前将值转换为浮点数,(2) 拒绝无法转换为浮点数的输入,以及 (3) 将空字符串转换为 NULL。 (1)和(2)按预期工作,但最后一位不起作用。在调试方法.submitAll() 时,它不会在self.record(row).setNull(col) 行上引发异常,但它似乎也没有效果。一个空字符串被发送并保存在数据库中。任何想法为什么以及如何解决它?

【问题讨论】:

QSqlTableModel.record 是 const。您需要使用setRecord。 我会试一试,但self.record(row).setValue(col, float(val)) 有效。只是self.record(row).setNull(col) 没有将值设置为NULL,而是一个空字符串。实际上,我错误地指出它似乎没有效果。它也可以,只需将值设置为空字符串。如果我通过删除数字条目来编辑记录,它将保留在数据库中,但作为空字符串而不是NULL 这可能是 SQLite 特有的,因为它没有“真正的”数据类型。您可以将任何数据类型插入任何列。我在其他地方没有遇到这个问题,因为我正在使用 sqlalchemy 来解决这个问题。在其他后端,无法将字符串插入数字列。 我从不使用记录来设置值,所以我只是假设setRcord 是必要的,因为其他几个 Qt API 都是这样工作的。你试过setValue(col, None)吗? 是的,但是结果和setNull一模一样 【参考方案1】:

我认为不需要重写submitAll() 方法,而是可以在setModelData() 方法中实现逻辑:

import sys

from PySide2 import QtCore, QtWidgets, QtSql

TABLENAME = "t1"


def create_connection():
    db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
    db.setDatabaseName("database.db")
    if not db.open():
        QtWidgets.QMessageBox.critical(
            None,
            QtWidgets.QApplication.instance().tr("Cannot open database"),
            QtWidgets.QApplication.instance().tr(
                "Unable to establish a database connection.\n"
                "This example needs SQLite support. Please read "
                "the Qt SQL driver documentation for information "
                "how to build it.\n\n"
                "Click Cancel to exit."
            ),
            QtWidgets.QMessageBox.Cancel,
        )
        return False

    if TABLENAME in db.tables():
        return True

    query = QtSql.QSqlQuery()
    if not query.exec_(f"CREATE TABLE IF NOT EXISTS TABLENAME(a REAL, b text);"):
        print(query.lastError().text())

    queries = (
        f"INSERT INTO TABLENAME VALUES(1, '1');",
        f"INSERT INTO TABLENAME VALUES(NULL, '2');",
        f"INSERT INTO TABLENAME VALUES(3, '3');",
        f"INSERT INTO TABLENAME VALUES(NULL, '4');",
        f"INSERT INTO TABLENAME VALUES(5, '4');",
        f"INSERT INTO TABLENAME VALUES(NULL, '5');",
        f"INSERT INTO TABLENAME VALUES(NULL, '7');",
    )

    for query in queries:
        q = QtSql.QSqlQuery()
        if not q.exec_(query):
            print(q.lastError().text(), query)
            return False

    return True


class ReadOnlyDelegate(QtWidgets.QStyledItemDelegate):
    def editorEvent(self, *args, **kwargs):
        return False

    def createEditor(self, *args, **kwargs):
        return None


class LabelDelegate(QtWidgets.QStyledItemDelegate):
    def createEditor(self, parent, options, index):
        le = QtWidgets.QLineEdit(parent)
        return le

    def setModelData(self, editor, model, index):
        value = editor.text()
        if not value:
            model.setData(index, None, QtCore.Qt.EditRole)
        else:
            try:
                number = float(value)
            except (TypeError, ValueError):
                print(
                    f"Can not convert to float, The value value could not be converted to float'"
                )
            else:
                model.setData(index, number, QtCore.Qt.EditRole)


def main(args):
    app = QtWidgets.QApplication(args)

    if not create_connection():
        sys.exit(-1)

    view = QtWidgets.QTableView()

    model = QtSql.QSqlTableModel()
    model.setTable(TABLENAME)
    model.setEditStrategy(QtSql.QSqlTableModel.OnManualSubmit)
    model.select()

    view.setModel(model)

    label_delegate = LabelDelegate(view)
    view.setItemDelegateForColumn(0, label_delegate)

    readonly_delegate = ReadOnlyDelegate(view)
    view.setItemDelegateForColumn(1, readonly_delegate)

    button = QtWidgets.QPushButton("Submit all")

    widget = QtWidgets.QWidget()
    lay = QtWidgets.QVBoxLayout(widget)
    lay.addWidget(button)
    lay.addWidget(view)

    widget.resize(640, 480)
    widget.show()

    button.clicked.connect(model.submitAll)

    ret = app.exec_()
    sys.exit(ret)


if __name__ == "__main__":
    main(sys.argv)

在 Linux 上测试:

PyQt5 5.15.4 PySide2 5.15.2 Python 3.9.4

【讨论】:

以上是关于如何使用 QSqlTableModel 在可编辑的 QTableView 中将值设置为 NULL的主要内容,如果未能解决你的问题,请参考以下文章

将行插入 QSqlTableModel

使用键盘时如何在可编辑视图中获取光标的位置?

在可编辑网格中,如何使 Ext 组合框在选择项目时立即完成编辑模式?

如何在可编辑的 ComboBox 中设置 TextInput 的“maxChars”?

如何在可编辑的 TextArea 中禁用回车?

如何在可编辑的 DIV 中输出美化的原始 HTML 代码