使用 QSQLTable 模型和 QDataWidget 映射器在 Sqlite table 中插入记录时出现问题

problem inserting record in Sqlite table using QSQLTable model and QDataWidget mapper

我正在努力处理下面显示的代码,该代码使用 QSQLTable 从具有 3 列(“codice”(主键,自动增量)、“descrizione”、“peso”)的 SQLite table 加载数据带有 QDataWidget 映射器的模型。

我的问题是我定义了一个 QPushButton 以在 table 中插入新记录,但我无法让它工作。我尝试了使用 self.model.insertRows 或 self.model.insertRecord 的不同方法,但没有成功。我没有收到任何错误,但没有插入记录。

确实存在我无法理解的概念错误。

import sys

from PyQt5.QtCore import QSize, Qt
from PyQt5.QtSql import QSqlDatabase, QSqlTableModel

from PyQt5.QtWidgets import (
    QApplication,
    QComboBox,
    QDataWidgetMapper,
    QDoubleSpinBox,
    QFormLayout,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QMainWindow,
    QPushButton,
    QSpinBox,
    QTableView,
    QVBoxLayout,
    QWidget,
)
from connect_SQLITE import Database

db=Database.con
db.open()

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        layout = QVBoxLayout()

        form = QFormLayout()

        self.codice = QSpinBox()
        self.codice.setRange(0, 2147483647)
        self.codice.setDisabled(True)
        
        self.descrizione = QLineEdit()
        
        self.peso = QDoubleSpinBox()
        self.peso.setDecimals(5)
        self.peso.setRange(0, 9999.99999)
        self.peso.setSingleStep(0.01)

        form.addRow(QLabel("Codice Materiale"), self.codice)
        form.addRow(QLabel("Nome Materiale"), self.descrizione)
        form.addRow(QLabel("Peso specifico Materiale (Kg/dm3)"), self.peso)

        self.model = QSqlTableModel(db=db)
        self.mapper = QDataWidgetMapper()
        self.mapper.setModel(self.model)

        self.mapper.addMapping(self.codice, 0)
        self.mapper.addMapping(self.descrizione, 1)
        self.mapper.addMapping(self.peso, 2)

        self.model.setTable("Prova")
        self.model.select()

        self.mapper.toFirst()

        # tag::controls[]
        self.setMinimumSize(QSize(400, 400))

        controls = QHBoxLayout()

        prev_rec = QPushButton("Precedente")
        prev_rec.clicked.connect(self.mapper.toPrevious)

        next_rec = QPushButton("Successivo")
        next_rec.clicked.connect(self.mapper.toNext)

        ins_rec = QPushButton("Inserimento")
        ins_rec.clicked.connect(self.inserimento_materiale)
        
        save_rec = QPushButton("Salvataggio modifiche")
        save_rec.clicked.connect(self.mapper.submit)

        controls.addWidget(prev_rec)
        controls.addWidget(next_rec)
        controls.addWidget(ins_rec)
        controls.addWidget(save_rec)

        layout.addLayout(form)
        layout.addLayout(controls)

        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)
        # end::controls[]

    def inserimento_materiale(self):
        self.model.insertRows(self.model.rowCount(), 1)
        self.model.setData(self.model.index(0,1), self.descrizione.text())
        self.model.setData(self.model.index(0,2), self.peso)
        self.model.submit()
     

app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

问题是映射器默认使用 AutoSubmit submitPolicy():

Whenever a widget loses focus, the widget's current value is set to the item model.

而 QSqlTableModel 使用 OnRowChange editStrategy():

Changes to a row will be applied when the user selects a different row.

此外,您在第一行调用 setData(),而不是 new

结果是当inserimento_materiale被调用并插入新行时,mapper的当前索引仍然是之前的索引(启动时第一个,由于toFirst()或任何toPrevious()toNext() 之后的其他设置),setData() 在任何情况下都不起作用:

  • 在你的例子中,映射器由于其AutoSubmit策略已经设置了缓存数据,但尚未实际提交到数据库中,并且setData() returns False 因为“对于 OnRowChange,只有当其他行没有缓存更改时,索引才可能接收更改”;
  • 即使为 setData() 设置正确的行也不起作用,因为插入新行会导致映射器提交前一行中当前小部件的先前数据,并且不会正确更新新映射器索引;

请注意,您的代码也有拼写错误:您正在尝试使用 self.peso 调用 setData(),这是一个小部件。

一个可能的解决方案是在执行任何操作之前读取当前小部件值revert()模型数据(以避免将新数据保存在先前的索引中),将新数据提交给模型并在映射器中设置新索引:

    def inserimento_materiale(self):
        descrizione = self.descrizione.text()
        peso = self.peso.value()
        self.model.revert()
        row = self.model.rowCount()
        self.model.insertRow(row)
        self.model.setData(self.model.index(row, 1), descrizione)
        self.model.setData(self.model.index(row, 2), peso)
        self.model.submitAll()
        self.mapper.setCurrentIndex(row)

请注意,由于您有一个专用的提交按钮,您应该将映射器的提交策略更改为 ManualSubmit,这会使事情变得更容易:

class MainWindow(QMainWindow):
    def __init__(self):
        # ...
        self.mapper.setSubmitPolicy(self.mapper.ManualSubmit)

    def inserimento_materiale(self):
        row = self.model.rowCount()
        self.model.insertRow(row)
        self.model.setData(self.model.index(row, 1), self.descrizione.text())
        self.model.setData(self.model.index(row, 2), self.peso.value())
        self.model.submitAll()
        self.mapper.setCurrentIndex(row)