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 条记录)。
我正在使用模型视图架构做第一步,我创建了一个简单的程序(参见下面的代码)从 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 需要更严格的布局,记录遵守列布局(可能还有它们的数据类型)并具有定义的计数。
电子表格不是这样工作的:您可以在每个索引上设置任意数据,而行数或列数并不重要。
也就是说,虽然我完全同意
创建新的 批处理 行或列显然不是可行的解决方案:
- 更改数据库的列布局不是立竿见影的,而且可能变得极其(且不必要)复杂;
- 添加一堆行是没有意义的:您需要检查每个新添加的行是否具有有效数据,并最终决定是否忽略它,但是如果以前忽略的行被编辑为有效数据怎么办?
考虑到上述情况,一个可能的解决方案是创建一个允许轻松插入新记录的系统。要实现这一点,有多种可能的方法,但是,首先您需要创建一个正确的方法来实际添加新记录并开始编辑:
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 条记录)。