python - QTableView 与 QWidget 作为 QStyledItemDelegate
问题描述
我正在编写一个 Python CRUD 应用程序,该应用程序在地图和 QTableView 上显示无线电探空仪。我正在使用 QStyledItemDelegate 为每一列设置一个编辑器和正则表达式验证器,它工作得很好。但是对于几何列,我想解析二进制数据并将其显示在自定义表单(纬度、经度、海拔)上,能够编辑它们,如果单击确定,将它们编码回 WKB 格式并更新数据。
当我单击确定时,该文件未更新,而是变为空。如果我在此之后尝试编辑任何其他单元格,则不会发生任何事情,并且如果我尝试编辑该确切单元格,则应用程序将崩溃。如果我单击取消,也会发生同样的情况。
setData 方法返回 True 并且数据库中的数据得到更新。
我尝试使用 QSqlTableModel 上的 dataChanged.emit() 以及 QTableView 上的 update() 方法。
main2.py:
from PyQt5.Qt import QStyledItemDelegate
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtCore import Qt
from shapely import wkb, wkt
import folium
import io
class Ui_RadioSondes(object):
def setupUi(self, RadioSondes):
self.centerCoord = (44.071800, 17.578125)
RadioSondes.setObjectName("RadioSondes")
.
.
.
self.tableView_2.setItemDelegate(ValidatedItemDelegate())
.
.
.
class ValidatedItemDelegate(QStyledItemDelegate):
def createEditor(self, widget, option, index):
if not index.isValid():
return 0
if index.column() == 0: #only on the cells in the first column
editor = QtWidgets.QLineEdit(widget)
validator = QtGui.QRegExpValidator(QtCore.QRegExp('[\w]{1,10}'), editor)
editor.setValidator(validator)
return editor
if index.column() == 2:
editor = QtWidgets.QSpinBox(widget)
editor.setMaximum(360)
editor.setMinimum(1)
return editor
.
.
.
if index.column() == 9:
self.form = QtWidgets.QWidget()
self.formLayout = QtWidgets.QFormLayout(self.form)
self.formLayout.setVerticalSpacing(12)
self.formLayout.setObjectName("formLayout")
###__________ Latitude__________###
self.latLabel = QtWidgets.QLabel(self.form)
self.latLabel.setObjectName("latLabel")
self.latLabel.setText("Latitude")
self.latLabel.adjustSize()
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.latLabel)
self.latEdit = QtWidgets.QLineEdit(self.form)
# lineEdit.textChanged.connect(validateFields)
self.latEdit.setObjectName("latEdit")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.latEdit)
###__________ Longitude__________###
self.lngLabel = QtWidgets.QLabel(self.form)
self.lngLabel.setObjectName("lngLabel")
self.lngLabel.setText("Longitude")
self.lngLabel.adjustSize()
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.lngLabel)
self.lngEdit = QtWidgets.QLineEdit(self.form)
# lineEdit.textChanged.connect(validateFields)
self.lngEdit.setObjectName("lngEdit")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.lngEdit)
###__________ Elevation__________###
self.elevationLabel = QtWidgets.QLabel(self.form)
self.elevationLabel.setObjectName("elevationLabel")
self.elevationLabel.setText("Elevation")
self.elevationLabel.adjustSize()
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.elevationLabel)
self.elevationEdit = QtWidgets.QLineEdit(self.form)
# lineEdit.textChanged.connect(validateFields)
self.elevationEdit.setObjectName("elevationEdit")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.elevationEdit)
self.buttonBox = QtWidgets.QDialogButtonBox(self.form)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.formLayout.addWidget(self.buttonBox)
self.form.resize(200, 300)
self.prevData = index.data()
self.index = index
self.widget = widget
self.model = self.widget.parent().parent().parent().parent().parent().parent().parent().objModel
self.t_view = self.widget.parent().parent().parent().parent().parent().parent().parent().tableView_2
data = self.model.data(self.index)
geomWkb = wkb.loads(bytes.fromhex(data))
self.latEdit.setText(str(geomWkb.x))
self.lngEdit.setText(str(geomWkb.y))
self.elevationEdit.setText(str(geomWkb.z))
self.buttonBox.accepted.connect(self.generateGeom)
self.buttonBox.rejected.connect(self.cancelGeomEdit)
return self.form
return super(ValidatedItemDelegate, self).createEditor(widget, option, index)
def generateGeom(self):
print(self.latEdit.text())
print(self.lngEdit.text())
print(self.elevationEdit.text())
geomStr = "POINT Z (" + self.latEdit.text() + " " + self.lngEdit.text() + " " + self.elevationEdit.text() + ")"
geom = wkt.loads(geomStr)
geomWkb = wkb.dumps(geom, hex=True, srid=4326)
try:
self.model.setData(self.index, geomWkb, Qt.EditRole)
self.form.close()
#self.t_view.update()
except AssertionError as error:
print(error)
def cancelGeomEdit(self):
self.form.destroy(destroyWindow=True)
这是 GitHub 上的完整代码:https ://github.com/draugnim/pyCrud
编辑
我设法通过self.model.selet()
在 generateGeom() 和 cancelGeomEdit() 结束时调用它来使其工作。但是,如果我单击 X 按钮并关闭表单,则编辑的单元格将变为空白,此单元格和所有其他单元格也将变为不可编辑。
解决方案
项目委托使用编辑器的用户属性,它被认为是 Qt 对象的主要默认属性。对于 QLineEdit 它是text()
,对于 QSpinBox 它是value()
,等等。
如果您想提供自定义的高级编辑器,解决方案是创建具有自定义用户属性的子类。
请注意,Qt 内部处理项目数据的方式对类型有点严格,并且由于 PyQt 不公开 QVariant 类,唯一的选择是使用合适的类型。对于您的情况,QVector3D是一个完美的选择。
然后,使用外部窗口有点棘手,因为委托通常应该是存在于视图中的简单字段编辑器。为了解决这个问题,必须考虑以下几点:
- 编辑器必须通知代理插入的数据已被接受,并且它必须在关闭时自行销毁;
- 过滤器中必须忽略按键事件(return
False
),以便编辑器正确处理它们; - focus 和 hide 事件也必须忽略,因为默认情况下,当编辑器没有被“拒绝”但失去焦点或被隐藏时,委托会尝试更新模型;
- 几何图形必须使用父级的顶层设置
window()
,并且updateEditorGeometry()
必须被忽略,因为每当视图在隐藏或调整大小后再次显示时,委托都会尝试更新几何图形;
由于给定的代码不是最小的、可重现的示例,因此我将提供该概念的通用示例。
from PyQt5 import QtCore, QtGui, QtWidgets
from random import randrange
class CoordinateEditor(QtWidgets.QDialog):
submit = QtCore.pyqtSignal(QtWidgets.QWidget)
def __init__(self, parent):
super().__init__(parent)
self.setWindowModality(QtCore.Qt.WindowModal)
layout = QtWidgets.QFormLayout(self)
self.latitudeSpin = QtWidgets.QDoubleSpinBox(minimum=-90, maximum=90)
layout.addRow('Latitude', self.latitudeSpin)
self.longitudeSpin = QtWidgets.QDoubleSpinBox(minimum=-180, maximum=180)
layout.addRow('Longitude', self.longitudeSpin)
self.elevationSpin = QtWidgets.QDoubleSpinBox(minimum=-100, maximum=100)
layout.addRow('Elevation', self.elevationSpin)
buttonBox = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Cancel)
layout.addRow(buttonBox)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
self.finished.connect(self.deleteLater)
def accept(self):
super().accept()
self.submit.emit(self)
@QtCore.pyqtProperty(QtGui.QVector3D, user=True)
def coordinateData(self):
return QtGui.QVector3D(
self.longitudeSpin.value(),
self.latitudeSpin.value(),
self.elevationSpin.value()
)
@coordinateData.setter
def coordinateData(self, data):
self.longitudeSpin.setValue(data.x())
self.latitudeSpin.setValue(data.y())
self.elevationSpin.setValue(data.z())
def showEvent(self, event):
if not event.spontaneous():
geo = self.geometry()
geo.moveCenter(self.parent().window().geometry().center())
self.setGeometry(geo)
QtCore.QTimer.singleShot(0, self.latitudeSpin.setFocus)
class DialogDelegate(QtWidgets.QStyledItemDelegate):
def createEditor(self, parent, option, index):
if index.column() == 1:
editor = CoordinateEditor(parent)
editor.submit.connect(self.commitData)
return editor
else:
return super().createEditor(parent, option, index)
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
if index.column() == 1 and index.data() is not None:
coords = index.data()
option.text = '{:.02f}, {:.02f}, {:.02f}'.format(
coords.y(), coords.x(), coords.z())
def eventFilter(self, source, event):
if isinstance(source, CoordinateEditor):
if event.type() in (event.KeyPress, event.FocusOut, event.Hide):
return False
return super().eventFilter(source, event)
def updateEditorGeometry(self, editor, option, index):
if not isinstance(editor, CoordinateEditor):
super().updateEditorGeometry(editor, option, index)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
test = QtWidgets.QTableView()
test.setItemDelegate(DialogDelegate(test))
model = QtGui.QStandardItemModel(0, 2)
for row in range(10):
coordItem = QtGui.QStandardItem()
coords = QtGui.QVector3D(
randrange(-180, 181),
randrange(-90, 91),
randrange(-100, 101))
coordItem.setData(coords, QtCore.Qt.DisplayRole)
model.appendRow((
QtGui.QStandardItem('Data {}'.format(row + 1)),
coordItem,
))
test.setModel(model)
test.resizeColumnsToContents()
test.show()
sys.exit(app.exec_())
推荐阅读
- python - 通过 SNR 检测 EEG 数据中的 Alpha 波?Python
- blender - 我在 Maya 和 Blender 中遇到枢轴问题
- html - 如何控制导航栏中的链接边距和填充?
- groovy - 在 Eclipse Groovy 程序中找不到类异常
- django - Django TestCase 类 - 为什么有些方法是骆驼案例而另一些是蛇案例?
- wordpress - 我想在 wp 主题中添加插件文件
- javascript - 键入脚本左右合并对象
- mysql - SQL查询 - 如何通过计数函数获得等于1的行
- python - 遍历列表并替换某个索引中的实例?
- jvm - jvm代码缓存区是否存在于堆或元空间中?