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

How to set value to NULL in editable QTableView with QSqlTableModel

我有一个 QTableView 显示 SQLite 数据库中 table 的数据子集。 table 是 editable 仅适用于可为空的数字列。我创建了两个委托 - 一个用于只读列:

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

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

还有一个用于 editable 列:

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

table 由自定义 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) 上没有引发异常,但它似乎也没有任何效果。一个空字符串被发送并保存在数据库中。知道为什么以及如何解决它吗?

我认为不需要重写 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