You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt5中基于QSqlRelationalTableModel实现QAbstractTableModel的setData方法

问题解决:QSqlRelationalTableModel包装类的setData实现与总计列添加

一、修复setData方法的报错

报错原因是QSqlRelationalTableModel不支持[]下标访问,必须使用它提供的API方法来读写数据。

核心错误修正:

  1. data方法中的EditRole处理:原代码用self._model[index.row()][index.column()]获取编辑值,改为调用底层模型的data方法获取。
  2. 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()

关键说明:

  1. setData修复:通过调用底层模型的setData方法修改数据库数据,修改成功后同时触发当前单元格和对应行总计列的更新信号。
  2. 总计列实现:
    • 新增列通过columnCount扩展,表头通过headerData设置
    • 总计值通过遍历指定列累加计算,格式化后显示
    • 总计列通过flags限制为不可编辑状态

内容的提问来源于stack exchange,提问作者Saša Šoltić

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 16:01:08