PyQt - 实现一个 QAbstractTableModel 以在 QTableView 中显示

18

我希望能在PyQt表格中显示一个pandas数据框。虽然我已经有了一些进展,但还未能正确生成TableModel类。如果您能提供任何帮助,将不胜感激。

**注意:完整的示例代码在此处**

我正在努力生成一个有效的QtCore.QAbstractTableModel派生类。继续上一个有关QItemDelegates的问题后,我正在尝试从Pandas DataFrame生成一个表格模型以插入真实数据。我有可用的示例代码在这里,但如果我在Widget类中(第152行)使用TableModel2代替我的TableModel,则无法使表格显示。

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表格示例。https://github.com/pydata/pandas/commits/master/pandas/sandbox/qtpandas.py - Kenji Noguchi
4个回答

18

好的,我根据上述建议和Summerfield的《Rapid GUI》书籍的一些帮助,已经想出了解决方法。QAbstractTableModel中不存在任何底层模型。只需覆盖三个函数,并且数据可以以任何用户定义的格式存储,只要在 data 调用中返回即可。

一个非常简单的实现方式可能是:

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视图中查看任何兼容的数据框。

我已更新此处的Gist。

如果您也需要执行此操作,这应该可以让您快速上手。


很遗憾,列和索引名称无法显示?如果您能修复这个问题,那就太好了。 - working4coins

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。
如果没有类型检查,你会立即发现这个问题。你真的想默默地忽略所有情况,即当你的类型不匹配时(最好让它引发一个错误,如果它不遵循你期望的相同接口)吗?类型检查在大多数情况下是不必要的

是的,我知道这是错误的,但这是我的问题。我认为它不应该设置为QTableWidgetItem。我想更新QAbstractTableModel中的内部模型,但我不确定如何做到这一点 - 或者找到行或列计数。谢谢。 - C Mars
我已经简化了示例代码,删除了这个dataFrameToQtTable,希望问题更加清晰。 - C Mars

1

0
这是一个最小的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_()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接