如何从PyQt QTableView中获取行的原始数据?
问题描述
我正在开发一个用QTableView展示信息的Python应用,当用户双击表格行时,想提取该行的原始模型数据做后续操作。但QAbstractTableModel的data()方法返回的是显示用的字符串,不是原始数据——比如单元格里的整数,我要的是实际的int类型,而不是转成字符串后再转回来(复杂场景下这种转换不可行)。
目前我是通过单独存储一份数据列表,双击时用索引去查找,虽然能用,但总觉得应该有更简洁的实现方式。有没有更好的从Qt模型中提取原始数据的方法?
现有代码
模型类代码
from PyQt5 import QtCore from PyQt5.QtCore import Qt from ResourceCard import ResourceCard class ResourceCardModel( QtCore.QAbstractTableModel): ''' classdocs ''' _headers = ["Level", "Color", "Points", "Cost"] def __init__(self, cards: list[ ResourceCard]): ''' Constructor ''' super(ResourceCardModel, self).__init__() self._data = cards def data(self, index, role): if role == Qt.DisplayRole or role == Qt.EditRole: # Look up the key by header index. column = index.column() row = index.row() item:ResourceCard = self._data[row] match column: case 0: return str(item.level) case 1: return str(item.suit.name) case 2: return str(item.points) case 3: return str(item.cost) case _: return "" def rowCount(self, index): # The length of the outer list. return len(self._data) def columnCount(self, index): # The length of our headers. return len(self._headers) def headerData(self, section, orientation, role): # section is the index of the column/row. if role == Qt.DisplayRole: if orientation == Qt.Horizontal: return str(self._headers[section]) if orientation == Qt.Vertical: return ""
主类相关代码
def __init__(self, *args, obj=None, **kwargs): ''' Constructor ''' QtWidgets.QMainWindow.__init__(self) Ui_Widget.__init__(self) self.setupUi(self) self.players = [] self.gameActions = GameActions(["p1", "p2", "p3", "p4"]) self.players = self.gameActions.getPlayersList() self.cards:dict[int,list] = self.gameActions.game.availableResources self.lvOneModel = ResourceCardModel( self.cards.get(1)) self.lvOneCardsTable.setModel(self.lvOneModel) self.lvOneCardsTable.doubleClicked.connect(self.availableCardDoubleClicked) def availableCardDoubleClicked(self, item:QModelIndex): msgDialog = QtWidgets.QMessageBox(self) itemData = self.cards.get(1)[item.row()] msgDialog.setText(str(itemData)) msgDialog.show()
解决方案
方法1:自定义数据角色(推荐)
Qt允许自定义角色(Role)来存储和获取原始数据,这种方式完全符合Qt的MVC设计模式,能保证数据一致性:
- 在模型类中定义自定义角色,用于返回原始数据
- 修改
data()方法,针对自定义角色返回对应原始值(可以是单个字段的原始类型,也可以是整个ResourceCard对象)
修改后的模型类示例:
from PyQt5 import QtCore from PyQt5.QtCore import Qt from ResourceCard import ResourceCard class ResourceCardModel(QtCore.QAbstractTableModel): _headers = ["Level", "Color", "Points", "Cost"] # 自定义角色,用于返回原始数据(可定义多个角色对应不同需求) RawCardRole = Qt.UserRole + 1 RawFieldRole = Qt.UserRole + 2 def __init__(self, cards: list[ResourceCard]): super().__init__() self._data = cards def data(self, index, role): if not index.isValid(): return None row = index.row() column = index.column() item: ResourceCard = self._data[row] if role == Qt.DisplayRole or role == Qt.EditRole: match column: case 0: return str(item.level) case 1: return str(item.suit.name) case 2: return str(item.points) case 3: return str(item.cost) case _: return "" # 返回整行的原始ResourceCard对象 elif role == self.RawCardRole: return item # 返回对应列的原始字段值 elif role == self.RawFieldRole: match column: case 0: return item.level case 1: return item.suit case 2: return item.points case 3: return item.cost case _: return None return None # rowCount、columnCount、headerData方法保持不变
主类中的双击事件可改为:
def availableCardDoubleClicked(self, index: QtCore.QModelIndex): msgDialog = QtWidgets.QMessageBox(self) # 获取整行的原始ResourceCard对象 rawCard = index.model().data(index, ResourceCardModel.RawCardRole) msgDialog.setText(str(rawCard)) msgDialog.show() # 如果需要单个字段的原始值(比如Level的整数类型): # rawLevel = index.model().data(index, ResourceCardModel.RawFieldRole) # print(type(rawLevel)) # 输出 <class 'int'>
方法2:给模型添加公共方法获取原始数据
如果不想自定义角色,可以直接在模型类中添加一个公共方法,用来返回指定行的原始数据:
class ResourceCardModel(QtCore.QAbstractTableModel): # ... 其他代码 ... def getRowRawData(self, row: int) -> ResourceCard: """返回指定行的原始ResourceCard对象""" if 0 <= row < len(self._data): return self._data[row] return None
然后在主类的双击事件中调用:
def availableCardDoubleClicked(self, index: QtCore.QModelIndex): msgDialog = QtWidgets.QMessageBox(self) rawCard = self.lvOneModel.getRowRawData(index.row()) msgDialog.setText(str(rawCard)) msgDialog.show()
为什么不推荐当前的方案?
你现在单独在主类中维护一份数据列表,一旦模型中的数据发生更新(比如添加、删除行),主类中的列表必须同步修改,否则会出现数据不一致的问题。而上面两种方法都是直接从模型获取数据,完全遵循Qt的MVC设计理念,能避免这类问题。
内容的提问来源于stack exchange,提问作者pbuchheit
相关产品推荐
相关产品推荐

