QStyledItemDelegate使用QWidget作为编辑器时QTableView编辑异常问题
问题核心原因
你当前的实现完全绕开了QStyledItemDelegate的标准编辑生命周期,导致出现一系列异常:
- 你手动在按钮点击事件中调用
setData写入数据库,但编辑器关闭后,delegate的默认流程还会自动执行setModelData从编辑器取值,此时你的编辑器已经被销毁,取到空值覆盖了model中已更新的数据,所以界面显示为空。 - 你把编辑器、index、model等对象存为delegate的实例属性,多次打开编辑会覆盖原有引用,产生野指针,导致后续操作无响应、程序崩溃。
- 层层遍历parent获取model和tableview的写法极不稳定,控件层级稍有变化就会报错。
- 没有处理表单右上角关闭事件,点击关闭时delegate默认视为提交空数据,导致单元格变空,编辑状态锁死。
- 调用
model.select()属于暴力刷新,会重置整个model的编辑状态,是治标不治本的方案。
具体修复方案
第一步:封装独立的坐标编辑对话框
单独把geometry编辑功能封装为QDialog子类,自带模态属性和关闭事件处理,避免污染delegate的属性:
class GeomEditDialog(QtWidgets.QDialog): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("编辑坐标") formLayout = QtWidgets.QFormLayout(self) formLayout.setVerticalSpacing(12) # 纬度输入 self.latEdit = QtWidgets.QLineEdit() formLayout.addRow("Latitude", self.latEdit) # 经度输入 self.lngEdit = QtWidgets.QLineEdit() formLayout.addRow("Longitude", self.lngEdit) # 高程输入 self.elevationEdit = QtWidgets.QLineEdit() formLayout.addRow("Elevation", self.elevationEdit) # 按钮组 self.buttonBox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) formLayout.addWidget(self.buttonBox) self.resize(200, 300) # 注入初始坐标数据 def set_init_geom(self, geom): self.latEdit.setText(str(geom.x)) self.lngEdit.setText(str(geom.y)) self.elevationEdit.setText(str(geom.z)) # 获取编辑后的WKB格式坐标 def get_result_wkb(self): geom_str = f"POINT Z ({self.latEdit.text()} {self.lngEdit.text()} {self.elevationEdit.text()})" geom = wkt.loads(geom_str) return wkb.dumps(geom, hex=True, srid=4326) # 处理右上角关闭事件,等同于点击取消 def closeEvent(self, event): self.reject() event.accept()
第二步:重写Delegate的标准生命周期方法
完全遵循delegate的标准流程,不要自己手动管理编辑器生命周期、手动调用setData:
class ValidatedItemDelegate(QStyledItemDelegate): def createEditor(self, widget, option, index): if not index.isValid(): return super().createEditor(widget, option, index) # 原有其他列的编辑器逻辑保持不变 if index.column() == 0: 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 # geometry列使用自定义对话框 if index.column() == 9: return GeomEditDialog(widget) return super().createEditor(widget, option, index) # 给编辑器填充初始值 def setEditorData(self, editor, index): if index.column() == 9 and isinstance(editor, GeomEditDialog): raw_data = index.data() geom = wkb.loads(bytes.fromhex(raw_data)) editor.set_init_geom(geom) return super().setEditorData(editor, index) # 将编辑器的数据写回model def setModelData(self, editor, model, index): if index.column() == 9 and isinstance(editor, GeomEditDialog): # 只有点击OK(对话框触发accept)才会执行到此处,取消/关闭不会触发 wkb_data = editor.get_result_wkb() model.setData(index, wkb_data, Qt.EditRole) return super().setModelData(editor, model, index)
修改后效果
- 点击OK按钮会自动提交数据,model更新后自动触发视图刷新,不会出现空值
- 点击Cancel或者右上角关闭按钮,不会提交数据,单元格保持原值
- 不需要手动调用
model.select()刷新,不会出现单元格锁死无法编辑的问题 - 没有野指针问题,不会出现程序崩溃
内容的提问来源于stack exchange,提问作者Draugnim
相关产品推荐
相关产品推荐

