PyQt如何实现QAbstractTableModel排序?

2024-06-16 09:36:34 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在使用PyQt5(5.7.1)和python3.5开发一个应用程序。我使用QTableView来显示一个很长的记录列表(超过10000条)。我想在同一时间对列表中的多个列进行排序。在

我尝试使用QAbstractTableModel和QSortFilterProxyModel,重新实现QSortFilterProxyModel.filterAcceptsRow()进行多列过滤(请参阅此博客文章:http://www.dayofthenewdan.com/2013/02/09/Qt_QSortFilterProxyModel.html)。但是,由于对每一行都调用此方法,所以当有大量行时,过滤非常慢。在

我认为使用Pandas进行过滤可以提高性能。因此,我创建了下面的pandablemodel类,它确实可以非常快速地执行多列过滤,甚至可以进行排序:

import pandas as pd
from PyQt5 import QtCore, QtWidgets


class PandasTableModel(QtCore.QAbstractTableModel):

    def __init__(self,  parent=None, *args):
        super(PandasTableModel,  self).__init__(parent,  *args)
        self._filters = {}
        self._sortBy = []
        self._sortDirection = []
        self._dfSource = pd.DataFrame()
        self._dfDisplay = pd.DataFrame()

    def rowCount(self,  parent=QtCore.QModelIndex()):
        if parent.isValid():
            return 0
        return self._dfDisplay.shape[0]

    def columnCount(self,  parent=QtCore.QModelIndex()):
        if parent.isValid():
            return 0
        return self._dfDisplay.shape[1]

    def data(self, index, role):
        if index.isValid() and role == QtCore.Qt.DisplayRole:
            return QtCore.QVariant(self._dfDisplay.values[index.row()][index.column()])
        return QtCore.QVariant()

    def headerData(self, col, orientation=QtCore.Qt.Horizontal, role=QtCore.Qt.DisplayRole):
        if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
            return QtCore.QVariant(str(self._dfDisplay.columns[col]))
        return QtCore.QVariant()

    def setupModel(self, header, data):
        self._dfSource = pd.DataFrame(data, columns=header)
        self._sortBy = []
        self._sortDirection = []
        self.setFilters({})

    def setFilters(self, filters):
        self.modelAboutToBeReset.emit()
        self._filters = filters
        self.updateDisplay()
        self.modelReset.emit()

    def sort(self, col, order=QtCore.Qt.AscendingOrder):
        #self.layoutAboutToBeChanged.emit()
        column = self._dfDisplay.columns[col]
        ascending = (order == QtCore.Qt.AscendingOrder)
        if column in self._sortBy:
            i = self._sortBy.index(column)
            self._sortBy.pop(i)
            self._sortDirection.pop(i)
        self._sortBy.insert(0, column)
        self._sortDirection.insert(0, ascending)
        self.updateDisplay()
        #self.layoutChanged.emit()
        self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())

    def updateDisplay(self):

        dfDisplay = self._dfSource.copy()

        # Filtering
        cond = pd.Series(True, index = dfDisplay.index)
        for column, value in self._filters.items():
            cond = cond & \
                (dfDisplay[column].str.lower().str.find(str(value).lower()) >= 0)
        dfDisplay = dfDisplay[cond]

        # Sorting
        if len(self._sortBy) != 0:
            dfDisplay.sort_values(by=self._sortBy,
                                ascending=self._sortDirection,
                                inplace=True)

        # Updating
        self._dfDisplay = dfDisplay

这个类复制QSortFilterProxyModel的行为,除了一个方面。如果在QTableView中选择了表中的某个项目,则对该表进行排序不会影响选择(例如,如果在排序之前选择了第一行,则排序后仍将选择第一行,而不是与之前相同的行。在

我认为问题与发射的信号有关。对于过滤,我使用了modelAboutBoreSet()和modelReset(),但是这些信号会取消QTableView中的选择,因此它们不适合排序。我在那里读到(How to update QAbstractTableModel and QTableView after sorting the data source?)layoutBouttoBeChanged()和layoutChanged()应该被发出。但是,如果我使用这些信号,QTableView不会更新(实际上我不明白为什么)。当排序完成后发出dataChanged()时,QTableView会更新,但会使用上面描述的行为(选择未更新)。在

可以使用以下示例测试此模型:

^{pr2}$

排序时如何使所选内容跟随所选元素?在


Tags: selfindexreturnif排序defcolumnqt
1条回答
网友
1楼 · 发布于 2024-06-16 09:36:34

多亏了Ekhurvo,我找到了解决办法。sort函数应该存储持久索引,创建新的索引并更改它们。下面是这样做的代码。对大量记录进行排序似乎有点慢,但这是可以接受的。在

def sort(self, col, order=QtCore.Qt.AscendingOrder):

    # Storing persistent indexes
    self.layoutAboutToBeChanged.emit()
    oldIndexList = self.persistentIndexList()
    oldIds = self._dfDisplay.index.copy()

    # Sorting data
    column = self._dfDisplay.columns[col]
    ascending = (order == QtCore.Qt.AscendingOrder)
    if column in self._sortBy:
        i = self._sortBy.index(column)
        self._sortBy.pop(i)
        self._sortDirection.pop(i)
    self._sortBy.insert(0, column)
    self._sortDirection.insert(0, ascending)
    self.updateDisplay()

    # Updating persistent indexes
    newIds = self._dfDisplay.index
    newIndexList = []
    for index in oldIndexList:
        id = oldIds[index.row()]
        newRow = newIds.get_loc(id)
        newIndexList.append(self.index(newRow, index.column(), index.parent()))
    self.changePersistentIndexList(oldIndexList, newIndexList)
    self.layoutChanged.emit()
    self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())

编辑:由于一个未知的原因,在最后发出改变的数据大大加快了排序速度。我试图用layoutBouttoBeChanged和layoutChanged(例如。self.layoutChanged.emit([], QtCore.QAbstractItemModel.VerticalSortHing)),但我得到一个错误,这些信号不带参数,考虑到Qt5文档中描述的这些信号的签名,这很奇怪。在

不管怎样,这段代码给了我预期的结果,所以已经是这样了。理解它的作用只是一个额外的收获!^^如果有人能解释的话,我很想知道。在

相关问题 更多 >