PyQt - 实现一个 QAbstractTableModel 以在 QTableView 中显示
Posted
技术标签:
【中文标题】PyQt - 实现一个 QAbstractTableModel 以在 QTableView 中显示【英文标题】:PyQt - Implement a QAbstractTableModel for display in QTableView 【发布时间】:2013-07-15 20:41:43 【问题描述】:我想在 PyQt 表中显示 pandas 数据框。我在这方面取得了一些进展,但无法正确派生 Table Model 类。对此的任何帮助将不胜感激。
** 注意完整的示例代码here **
我正在努力生成一个有效的 QtCore.QAbstractTableModel 派生类。继上一个关于 QItemDelegates 的问题之后,我正在尝试从 Pandas DataFrame 生成表模型以插入真实数据。我有工作示例代码here,但如果我在 Widget 类 (ln 152) 中将 TableModel 替换为 TableModel2,我无法让表格显示。
class TableModel2(QtCore.QAbstractTableModel):
def __init__(self, parent=None, *args):
super(TableModel2, self).__init__()
#QtCore.QAbstractTableModel.__init__(self, parent, *args)
self.datatable = None
self.headerdata = None
self.dataFrame = None
self.model = QtGui.QStandardItemModel(self)
def update(self, dataIn):
print 'Updating Model'
self.datatable = dataIn
print 'Datatable : 0'.format(self.datatable)
headers = dataIn.columns.values
header_items = [
str(field)
for field in headers
]
self.headerdata = header_items
print 'Headers'
print self.headerdata
for i in range(len(dataIn.index.values)):
for j in range(len(dataIn.columns.values)):
#self.datatable.setItem(i,j,QtGui.QTableWidgetItem(str(df.iget_value(i, j))))
self.model.setItem(i,j,QtGui.QStandardItem(str(dataIn.iget_value(i, j))))
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.datatable.index)
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self.datatable.columns.values)
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return QtCore.QVariant()
elif role != QtCore.Qt.DisplayRole:
return QtCore.QVariant()
#return QtCore.QVariant(self.model.data(index))
return QtCore.QVariant(self.model.data(index))
def headerData(self, col, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return QtCore.QVariant()
return QtCore.QVariant(self.headerdata[col])
def setData(self, index, value, role=QtCore.Qt.DisplayRole):
print "setData", index.row(), index.column(), value
def flags(self, index):
if (index.column() == 0):
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled
else:
return QtCore.Qt.ItemIsEnabled
我正在尝试创建模型,然后将其添加到视图中,如下所示:
class Widget(QtGui.QWidget):
"""
A simple test widget to contain and own the model and table.
"""
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
l=QtGui.QVBoxLayout(self)
cdf = self.get_data_frame()
self._tm=TableModel(self)
self._tm.update(cdf)
self._tv=TableView(self)
self._tv.setModel(self._tm)
for row in range(0, self._tm.rowCount()):
self._tv.openPersistentEditor(self._tm.index(row, 0))
l.addWidget(self._tv)
def get_data_frame(self):
df = pd.DataFrame('Name':['a','b','c','d'],
'First':[2.3,5.4,3.1,7.7], 'Last':[23.4,11.2,65.3,88.8], 'Class':[1,1,2,1], 'Valid':[True, True, True, False])
return df
感谢您的关注!
注意:编辑 2 我已将 QStandardItemModel 合并到 TableModel2 中。在@mata 的评论之后还删除了 dataFrameToQtTable 函数。这越来越近了,但仍然无法正常工作。
【问题讨论】:
Pandas 在其 repo 中有一个 Qt 表示例。 github.com/pydata/pandas/commits/master/pandas/sandbox/… 【参考方案1】:好的,我已经通过上述建议和 Summerfield 的 Rapid GUI 书的一些帮助解决了这个问题。 QAbstractTableModel 中不存在基础模型。只需要重写三个函数,数据可以以任何用户定义的格式存储,只要在数据调用中返回即可。
一个非常简单的实现可能是:
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, parent=None, *args):
super(TableModel, self).__init__()
self.datatable = None
def update(self, dataIn):
print 'Updating Model'
self.datatable = dataIn
print 'Datatable : 0'.format(self.datatable)
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.datatable.index)
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self.datatable.columns.values)
def data(self, index, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
i = index.row()
j = index.column()
return '0'.format(self.datatable.iget_value(i, j))
else:
return QtCore.QVariant()
def flags(self, index):
return QtCore.Qt.ItemIsEnabled
这使您可以在 Qt 视图中查看任何兼容的数据框。
我已通过 here 更新了 Gist
如果您还需要这样做,这应该可以帮助您快速完成。
【讨论】:
不幸的是,列名和索引名不显示?如果你能解决这个问题就好了。【参考方案2】:这可能是你的问题:
def rowCount(self, parent=QtCore.QModelIndex()):
if type(self.datatable) == pd.DataFrame:
...
def columnCount(self, parent=QtCore.QModelIndex()):
if (self.datatable) == pd.DataFrame:
...
您在dataFrameToQtTable
中将datatable
设置为QTableWidget
,因此它不能是pd.DataFrame
,您的方法将始终返回0。
如果没有类型检查,您会立即发现问题。您是否真的想默默地忽略所有类型不匹配的情况(如果它不遵循您期望的相同界面,最好让它引发错误)?类型检查是in most cases unnecessary。
【讨论】:
是的,我知道这是错误的,但这是我的问题。我不认为它应该设置为 QTableWidgetItem。我想更新 QAbstractTableModel 中的内部模型,但我不确定如何执行此操作 - 或查找行数或列数。谢谢。 我已简化示例代码以删除此 dataFrameToQtTable 希望问题更清楚。【参考方案3】:Pandas 0.13 作为实验性功能提供:
对 qtpandas DataFrameModel
和 DataFrameWidget
的 PySide 支持
见https://github.com/pydata/pandas/blob/master/doc/source/faq.rst
您可以使用添加此功能
from pandas.sandbox.qtpandas import DataFrameModel, DataFrameWidget
【讨论】:
【参考方案4】:这是一个最小的QAbstractItemModel
实现,展示了它是如何工作的。
# Created by BaiJiFeiLong@gmail.com at 2022/3/2
from __future__ import annotations
import typing
from PySide2 import QtWidgets, QtCore
class TreeNode(object):
columns: typing.Sequence[str]
children: typing.Sequence[TreeNode]
parent: typing.Union[None, TreeNode]
def __init__(self, columns, children) -> None:
self.columns = columns
self.children = children
self.parent = None
for child in self.children:
child.parent = self
def row(self):
return -1 if self.parent is None else self.parent.children.index(self)
class TreeModel(QtCore.QAbstractItemModel):
def __init__(self, parent: typing.Optional[QtCore.QObject] = None) -> None:
super().__init__(parent)
self._rootNode = TreeNode(columns=["Name", "Description"], children=[
TreeNode(columns=["Animal", "The Animal"], children=[
TreeNode(columns=["Ant", "The Ant"], children=[]),
TreeNode(columns=["Bee", "The Bee"], children=[]),
TreeNode(columns=["Cat", "The Cat"], children=[]),
]),
TreeNode(columns=["Plant", "The Plant"], children=[
TreeNode(columns=["Apple", "The Apple"], children=[]),
TreeNode(columns=["Banana", "The Banana"], children=[]),
]),
])
def _indexToNode(self, index: QtCore.QModelIndex):
return index.internalPointer() if index.isValid() else self._rootNode
def index(self, row: int, column: int, parent: QtCore.QModelIndex = ...) -> QtCore.QModelIndex:
return self.createIndex(row, column, self._indexToNode(parent).children[row])
def parent(self, child: QtCore.QModelIndex) -> QtCore.QModelIndex:
parent = self._indexToNode(child).parent
return QtCore.QModelIndex() if parent is None else self.createIndex(parent.row(), 0, parent)
def rowCount(self, parent: QtCore.QModelIndex = ...) -> int:
return len(self._indexToNode(parent).children)
def columnCount(self, parent: QtCore.QModelIndex = ...) -> int:
return len(self._indexToNode(parent).columns)
def data(self, index: QtCore.QModelIndex, role: int = ...) -> typing.Any:
if role == QtCore.Qt.ItemDataRole.DisplayRole:
return self._indexToNode(index).columns[index.column()]
def headerData(self, section: int, orientation: QtCore.Qt.Orientation, role: int = ...) -> typing.Any:
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self._rootNode.columns[section]
app = QtWidgets.QApplication()
treeView = QtWidgets.QTreeView()
treeView.setModel(TreeModel())
treeView.show()
app.exec_()
【讨论】:
以上是关于PyQt - 实现一个 QAbstractTableModel 以在 QTableView 中显示的主要内容,如果未能解决你的问题,请参考以下文章
PyQt - 重新实现 QSqlTableModel 的数据方法的麻烦
自定义实现 PyQt5 下拉复选框 ComboCheckBox