pyQT5 - 使用 QTableView 将记录添加到 SQLlite table

pyQT5 - Adding records to a SQLlite table using QTableView

我正在使用模型视图架构做第一步,我创建了一个简单的程序(参见下面的代码)从 SQLite table 加载数据以显示在表单中。 我使用了 QSqlTableModel 和 QTableView,代码按预期工作,显示了从指定的 table.

中选择的数据

现在我需要使用 QTableView 向 SQLite table 添加行,我想知道是否有一种“电子表格方式”的方法,这意味着在最后一行之后有一个空行QTableView 填充值,然后通过 QSqlTableModel 在 SQLlite table 中插入新记录。

如果没有,建议采用哪种方式来处理这种情况?使用 QPushButton 触发新记录的插入 ?

感谢您的帮助

import sys

from PyQt5 import QtWidgets
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtSql import QSqlDatabase, QSqlTableModel
from PyQt5.QtWidgets import QApplication, QTableView,QWidget,QMessageBox, QMainWindow
from connect_SQLITE import Database
from RisorseInterneUi import Ui_Form

class MainWindow(QWidget, Ui_Form):

def __init__(self):
    super().__init__()
    self.setupUi(self)

    self.model = QSqlTableModel(self)       
    self.model.setTable("tb_RisorseInterne")
    self.model.setEditStrategy(QSqlTableModel.OnFieldChange)
    self.model.setHeaderData(0, Qt.Horizontal, "CDL")
    self.model.setHeaderData(1, Qt.Horizontal, "Desc_CDL")
    self.model.setHeaderData(2, Qt.Horizontal, "Tipo")
    self.model.setHeaderData(3, Qt.Horizontal, "Desc_2")
    self.model.setHeaderData(4, Qt.Horizontal, "Costi")
    self.model.setHeaderData(5, Qt.Horizontal, "Troncone")
    self.model.setHeaderData(6, Qt.Horizontal, "BarraUtile")
    self.model.setHeaderData(7, Qt.Horizontal, "SovrametalloPerTaglio")
    self.model.setHeaderData(8, Qt.Horizontal, "CambioUtensile")
    self.model.setHeaderData(9, Qt.Horizontal, "TempoCSAutomatico")
    self.model.setHeaderData(10, Qt.Horizontal, "TempoRiposizCreSecPass")
    self.model.setHeaderData(11, Qt.Horizontal, "TempoRiposizCreCaricMANUALE")
    self.model.setHeaderData(12, Qt.Horizontal, "NrGiriMax-RPM")
    
    self.model.select()
    
    self.tableView.setModel(self.model)
    self.tableView.setSelectionMode(QTableView.SingleSelection)
    self.tableView.setSelectionBehavior(QTableView.SelectItems)
    


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

SQL-based 视图不打算用作电子表格,它更像是模型视图的“free-form”方式。

主要问题是 SQL 需要更严格的布局,记录遵守列布局(可能还有它们的数据类型)并具有定义的计数。

电子表格不是这样工作的:您可以在每个索引上设置任意数据,而行数或列数并不重要。

也就是说,虽然我完全同意 (最好有一个单独的 UI 用于数据编辑和插入),但在某些情况下 fast/direct 插入可能因 UI 一致性和易用性而成为首选。

创建新的 批处理 行或列显然不是可行的解决方案:

  • 更改数据库的列布局不是立竿见影的,而且可能变得极其(且不必要)复杂;
  • 添加一堆行是没有意义的:您需要检查每个新添加的行是否具有有效数据,并最终决定是否忽略它,但是如果以前忽略的行被编辑为有效数据怎么办?

考虑到上述情况,一个可能的解决方案是创建一个允许轻松插入新记录的系统。要实现这一点,有多种可能的方法,但是,首先您需要创建一个正确的方法来实际添加新记录并开始编辑:

class AddRecordTableView(QtWidgets.QTableView):
    def addRecordAndEdit(self):
        self.setCurrentIndex(QtCore.QModelIndex())
        row = self.model().rowCount()
        self.model().insertRow(row)
        firstColumn = self.horizontalHeader().logicalIndex(0)
        index = self.model().index(row, firstColumn)
        self.setCurrentIndex(index)
        if index.flags() & QtCore.Qt.ItemIsEditable:
            self.edit(index)
            # we assume that the new index is always the last row, so we
            # scroll to the bottom no matter what; if we implement a "fake
            # item" to add new records, this will ensure that it will always
            # be visible when editing begins
            QtCore.QTimer.singleShot(0, lambda: 
                self.verticalScrollBar().setValue(
                    self.verticalScrollBar().maximum()))

现在,我们可以只在单击某些按钮时调用该方法,但我们还可以考虑两种重要的可能性:

  • 用户在 last 项上完成编辑后使用 Tab
  • 最后(可见)行下方的用户 double-clicks;

所以,我们可以通过正确实现相关方法来做到这一点:

  • closeEditor() 会在 non-persistent 编辑器因 Tab 按键而失去焦点时被调用,如果我们最终可以调用 addRecordAndEdit知道 closing 索引是 table;
  • 的最后一个可见 row/column 每次用户 double-clicks 视口内容时都会调用
  • mouseDoubleClickEvent(),因此如果我们知道此时没有有效索引,我们可以决定创建一个新记录;
class AddRecordTableView(QtWidgets.QTableView):
    def addRecordAndEdit(self):
        # as above...

    def _lastColumn(self):
        if not self.model():
            return -1
        return self.horizontalHeader().logicalIndex(
            self.model().columnCount() 
            - self.horizontalHeader().hiddenSectionCount() - 1)

    def _lastRow(self):
        if not self.model():
            return -1
        return self.verticalHeader().logicalIndex(
            self.model().rowCount() 
            - self.verticalHeader().hiddenSectionCount() - 1)

    def closeEditor(self, editor, hint):
        if hint == QtWidgets.QAbstractItemDelegate.EditNextItem:
            current = self.currentIndex()
            if (current.row() == self._lastRow() and 
                current.column() == self._lastColumn()):
                    super().closeEditor(
                        editor, QtWidgets.QAbstractItemDelegate.NoHint)
                    QtCore.QTimer.singleShot(0, self.addRecordAndEdit)
                    return
        super().closeEditor(editor, hint)

    def canAddRowFromEventAtPos(self, pos):
        # we will need this later...
        lastColumn = self._lastColumn()
        if lastColumn < 0:
            # this should not happen...
            return False

        lastRow = self._lastRow()
        rect = self.visualRect(self.model().index(lastRow, lastColumn))
        return rect.isNull() or pos.y() >= rect.bottom()

    def mouseDoubleClickEvent(self, event):
        super().mouseDoubleClickEvent(event)
        if (event.button() != QtCore.Qt.LeftButton or 
            self.indexAt(event.pos()).isValid()):
                return

        if self.canAddRowFromEventAtPos(event.pos()):
            self.addRecordAndEdit()

现在,这里有一个小问题:如果视图大小与当前行一样高,则没有地方 double-click 添加另一个项目。

我们需要添加更多垂直 space 以便显示“假项目”,允许用户 double-click 它以添加新记录。

为此,我实现了另一个子类(但您可能会选择将其合并为一个)。这为视图添加了一个进一步的垂直“边距”,并且还绘制了一个假项目,它将显示可以做什么的提示:只要你 double-click 那个“假项目”,你就会添加一个新记录.

class AddRecordTableViewFakeItem(AddRecordTableView):
    _addRect = None
    _hoverAdd = False
    def canAddRowFromEventAtPos(self, pos):
        # override for AddRecordTableView, we can only add new records as long
        # as the event pos is within the "add rect" boundaries
        return self._lastColumn() >= 0 and pos in self.addRect()

    def updateGeometries(self):
        super().updateGeometries()
        self._addRect = None

        # increase the maximum value of the vertical scroll bar in order to allow
        # scrolling *beyond* the last row
        viewHeight = self.viewport().size().height()
        vlength = self.verticalHeader().length()
        sectionSize = self.verticalHeader().defaultSectionSize()
        if (self.verticalScrollMode() == self.ScrollPerItem and 
            viewHeight < vlength + sectionSize):
                extend = 1
        elif viewHeight < vlength + sectionSize:
            extend = min(sectionSize, vlength + sectionSize - viewHeight)
        else:
            # the viewport is tall enough, ignore
            return

        self.verticalScrollBar().setMaximum(
            self.verticalScrollBar().maximum() + extend)

    def scrollContentsBy(self, dx, dy):
        super().scrollContentsBy(dx, dy)
        if dy:
            self._addRect = None
        if (self.verticalScrollMode() == self.ScrollPerItem and
            0 < self.verticalScrollBar().value() == self.verticalScrollBar().maximum()):
                self.verticalHeader().setOffset(
                    self.verticalHeader().offset() 
                    + self.verticalHeader().defaultSectionSize())
                self.viewport().scroll(0, -self.verticalHeader().defaultSectionSize())

    def viewportEvent(self, event):
        if event.type() in (event.HoverEnter, event.HoverMove, event.HoverLeave):
            addRect = self.addRect()
            hoverAdd = event.pos() in addRect
            if self._hoverAdd != hoverAdd:
                self._hoverAdd = hoverAdd
                self.viewport().update(addRect)
        return super().viewportEvent(event)

    def addRect(self):
        if self._addRect is None:
            lastRow = self._lastRow()
            if lastRow < 0:
                y = 0
            else:
                vr = self.visualRect(self.model().index(lastRow, 0))
                y = vr.y() + vr.height()
            if self.horizontalScrollBar().value() < self.horizontalScrollBar().maximum():
                width = self.viewport().width() - 1
            else:
                lastColumn = self._lastColumn()
                right = (self.horizontalHeader().sectionPosition(lastColumn)
                    + self.horizontalHeader().sectionSize(lastColumn))
                width = right - self.horizontalHeader().offset() - 1
            self._addRect = QtCore.QRect(0, y, width, 
                self.verticalHeader().defaultSectionSize() - 1)
        return self._addRect

    def enterEvent(self, event):
        super().enterEvent(event)
        self.viewport().update()

    def leaveEvent(self, event):
        super().leaveEvent(event)
        self.viewport().update()

    def paintEvent(self, event):
        super().paintEvent(event)
        model = self.model()
        if not model or not self.underMouse():
            return
        r = self.addRect()
        if r.y() > event.rect().bottom():
            return
        qp = QtGui.QPainter(self.viewport())
        qp.setRenderHint(qp.Antialiasing)
        qp.translate(.5, .5)
        if not self._hoverAdd:
            color = self.palette().color(QtGui.QPalette.Disabled, QtGui.QPalette.Text)
        else:
            color = self.palette().color(QtGui.QPalette.Text)
        color.setAlphaF(color.alphaF() * .75)
        qp.setPen(color)
        qp.setBrush(QtCore.Qt.NoBrush)
        qp.drawRoundedRect(r.adjusted(1, 1, -1, -1), 2, 2)
        fm = self.fontMetrics()
        text = fm.elidedText('Double click here to add record', QtCore.Qt.ElideRight, 
            r.width() - 4 - fm.horizontalAdvance(' ') * 2)
        qp.drawText(r, QtCore.Qt.AlignCenter, text)

注意:这仅使用基本的简单模型进行了测试。最重要的是,我没有使用 large 模型进行测试,这些模型会在 fetchMore() 上添加记录(通常默认为 255 条记录)。