PyQt5中基于QSqlRelationalTableModel实现QAbstractTableModel的setData方法
问题解决:QSqlRelationalTableModel包装类的setData实现与总计列添加
一、修复setData方法的报错
报错原因是QSqlRelationalTableModel不支持[]下标访问,必须使用它提供的API方法来读写数据。
核心错误修正:
- data方法中的EditRole处理:原代码用
self._model[index.row()][index.column()]获取编辑值,改为调用底层模型的data方法获取。 - setData方法实现:直接调用
QSqlRelationalTableModel自带的setData方法修改数据,而非尝试下标赋值。
二、实现总计列(行内计算列)
要添加总计列,需在自定义模型中修改以下方法:
columnCount:返回原模型列数+1,新增总计列headerData:为新增列设置表头标题(如"总计")data:在最后一列的DisplayRole中计算并返回该行指定列的总和flags:设置总计列不可编辑
完整修正代码
import sys from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtCore import Qt from PyQt5.QtWidgets import (QApplication, QHeaderView, QMessageBox, QTableView, QMainWindow) from PyQt5.QtSql import QSqlDatabase, QSqlRelationalTableModel from PyQt5.QtSql import QSqlRelation, QSqlRelationalDelegate class MyTableModel(QtCore.QAbstractTableModel): def __init__(self, model): super().__init__() self._model = model # 定义需要计算总和的列索引(根据你的业务调整,示例为列1) self.calculate_columns = [1] def data(self, index, role): # 处理总计列 if index.column() == self._model.columnCount(): if role == Qt.ItemDataRole.DisplayRole: total = 0 for col in self.calculate_columns: value = self._model.record(index.row()).value(col) if isinstance(value, (int, float)): total += value return "${: ,.2f}".format(total) return None # 处理原模型的列 value = self._model.record(index.row()).value(index.column()) if role == Qt.ItemDataRole.DisplayRole: if isinstance(value, int) and index.column() == 0: return f'# {value}' if isinstance(value, (int, float)) and index.column() == 1: return "${: ,.2f}".format(value) return value if role == Qt.ItemDataRole.EditRole: return self._model.data(self._model.index(index.row(), index.column()), Qt.EditRole) if role == Qt.ItemDataRole.DecorationRole: if isinstance(value, int) and index.column() == 0: return QtGui.QIcon('data/icons/hashtag_icon.png') if isinstance(value, str) and index.column() == 9: return QtGui.QIcon('data/icons/calendar.png') return None def headerData(self, section: int, orientation: Qt.Orientation, role: int): if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: if section == self._model.columnCount(): return "总计" return self._model.headerData(section, orientation, role) return None def rowCount(self, parent: QtCore.QModelIndex) -> int: return self._model.rowCount() def columnCount(self, parent: QtCore.QModelIndex) -> int: # 原列数+1(总计列) return self._model.columnCount() + 1 def setData(self, index, value, role): if not index.isValid(): return False # 总计列不可编辑 if index.column() == self._model.columnCount(): return False if role == Qt.EditRole: # 调用底层模型的setData修改数据 if self._model.setData(self._model.index(index.row(), index.column()), value, role): # 触发当前单元格和对应行总计列的更新 total_index = self.index(index.row(), self._model.columnCount()) self.dataChanged.emit(index, index) self.dataChanged.emit(total_index, total_index) return True return False def flags(self, index): # 总计列仅可选择和查看,不可编辑 if index.column() == self._model.columnCount(): return Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled return Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsEditable # 主窗口类 class MainWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle('QTable Example') self.window_width, self.window_height = 1000, 700 self.setMinimumSize(self.window_width, self.window_height) model = QSqlRelationalTableModel(self) model.setTable('obracundetails') model.setEditStrategy(QSqlRelationalTableModel.EditStrategy.OnFieldChange) model.setRelation(8, QSqlRelation('asortiman', 'asortiman_id', 'naziv')) model.setRelation(9, QSqlRelation('obracunmain', 'obracunmain_id', 'datum')) model.select() presentation_model = MyTableModel(model) view = QTableView(self) view.setModel(presentation_model) # 设置关系代理,确保关联列的下拉选择正常工作 view.setItemDelegate(QSqlRelationalDelegate(view)) view.resizeColumnsToContents() self.setCentralWidget(view) # 数据库连接 def createConnection() -> bool: con = QSqlDatabase.addDatabase('QSQLITE') con.setDatabaseName('havana.sqlite3') if not con.open(): QMessageBox.critical( None, 'QTableView Example - Error!', 'Database Error: %s' % con.lastError().databaseText(), ) return False return True if __name__ == "__main__": app = QApplication(sys.argv) if not createConnection(): sys.exit(1) form = MainWindow() form.show() app.exec()
关键说明:
- setData修复:通过调用底层模型的
setData方法修改数据库数据,修改成功后同时触发当前单元格和对应行总计列的更新信号。 - 总计列实现:
- 新增列通过
columnCount扩展,表头通过headerData设置 - 总计值通过遍历指定列累加计算,格式化后显示
- 总计列通过
flags限制为不可编辑状态
- 新增列通过
内容的提问来源于stack exchange,提问作者Saša Šoltić
相关产品推荐
相关产品推荐

