tableview 在对 tablemodel 排序后未更新

tableview not updated after sort on tablemodel

想法是通过 PyQt5 MV 编程习惯用法显示数据帧,并对显示的数据帧执行一些基本的排序和过滤操作。

显示部分一切正常,但现在我卡在了工具的排序部分。打印语句向我展示了它自己排序的数据框,它是未更新的视图。所以现在到代码:

import sys
import operator
tmp = [('23-02-1978', '19:03:13', 'eh', None, 'even more some data'),
       ('23-02-1978', '19:01:45',  'ss', 'some data ', 'even more some data'),
       ('23-02-1978', '19:02:55',  'he', 'some data ', 'even more some data')]    


tmp1 = [('23-02-1978', '19:02:33',  'eh', 'some data ', '666', 'even more some data'),
        ('23-02-1978', '19:03:22',  'ss', 'some data ', '777', 'even more some data'),
        ('23-02-1978', '19:01:45',  'he', 'some data ', '888', 'even more some data')]  


from PyQt5.QtWidgets import (QMainWindow, QApplication, QWidget, QAction,
                             QGroupBox, QCheckBox, QTableView, QTableWidgetItem, 
                             QTabWidget, QGridLayout,QLineEdit, QFormLayout, 
                             QVBoxLayout, QHBoxLayout, QLabel, QDialog, QHeaderView)

from PyQt5.QtGui import QIcon, QFont 
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal, QAbstractTableModel, QVariant, QModelIndex, QSortFilterProxyModel


from pandas import DataFrame


class DataFrameModel(QAbstractTableModel): 
    def __init__(self): 
        """ datain: a list of lists
            headerdata: a list of strings
        """
        super(DataFrameModel, self).__init__()
        self._df = DataFrame()

    def setDataFrame(self, df):
        self._df = df;

    def signalUpdate(self):
        ''' tell viewers to update their data (this is full update, not
        efficient)'''
        self.layoutChanged.emit()


    #------------- table display functions -----------------
    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if role != Qt.DisplayRole:
            return QVariant()

        if orientation == Qt.Horizontal:
            try:
                return self._df.columns.tolist()[section]
            except (IndexError, ):
                return QVariant()
        elif orientation == Qt.Vertical:
            try:
                # return self.df.index.tolist()
                return self._df.index.tolist()[section]
            except (IndexError, ):
                return QVariant()

    def data(self, index, role=Qt.DisplayRole):
        if role != Qt.DisplayRole:
            return QVariant()

        if not index.isValid():
            return QVariant()

        return QVariant(str(self._df.ix[index.row(), index.column()]))

    def flags(self, index):
            flags = super(DataFrameModel, self).flags(index)
            return flags

    def setData(self, index, value, role):
        row = self._df.index[index.row()]
        col = self._df.columns[index.column()]
        if hasattr(value, 'toPyObject'):
            # PyQt4 gets a QVariant
            value = value.toPyObject()
        else:
            # PySide gets an unicode
            dtype = self._df[col].dtype
            if dtype != object:
                value = None if value == '' else dtype.type(value)
        self._df.set_value(row, col, value)
        return True

    def rowCount(self, parent=QModelIndex()): 
        return len(self._df.index)

    def columnCount(self, parent=QModelIndex()): 
        return len(self._df.columns)

    def sort(self, column, order=Qt.AscendingOrder):
        """Sort table by given column number.
        """
        print('sort clicked col {} order {}'.format(column, order))
        self.layoutAboutToBeChanged.emit()
        print(self._df.columns[column])
        self._df.sort_values('time', ascending=order == Qt.AscendingOrder, inplace=True)
        print(self._df)
        self.layoutChanged.emit()


class DataFrameWidget(QWidget):
    ''' a simple widget for using DataFrames in a gui '''
    def __init__(self, dataFrame, parent=None):
        super(DataFrameWidget, self).__init__(parent)

        self.dataModel = DataFrameModel()
        # Set DataFrame
        self.dataTable = QTableView()
#        self.proxy = QSortFilterProxyModel()
#        self.proxy.setSourceModel(self.dataModel)
        self.dataTable.setModel(self.dataModel)

        self.dataTable.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)

        self.setDataFrame(dataFrame)
        self.dataTable.setSortingEnabled(True)
        self.dataTable.sortByColumn(0,0)


        layout = QVBoxLayout()
        layout.addWidget(self.dataTable)
        self.setLayout(layout)

    def setDataFrame(self, dataFrame):
        self.dataModel.setDataFrame(dataFrame)
        self.dataModel.signalUpdate()


def testDf():
    ''' creates test dataframe '''
#    data = {'int': [1, 2, 3], 'float': [1.5, 2.5, 3.5],
#            'string': ['a', 'b', 'c'], 'nan': [np.nan, np.nan, np.nan]}

#    data = [(1, 1.5, 'a', np.nan),
#            (2, 2.5, 'b', np.nan),
#            (3, 3.5, 'c', np.nan)]



    return DataFrame(tmp, columns=['date', 'time', 'string', 'nan', 'bla'])

class Form(QDialog):
    def __init__(self, parent=None):
        super(Form, self).__init__(parent)

        df = testDf()  # make up some data
        widget = DataFrameWidget(df)

        layout = QVBoxLayout()
        layout.addWidget(widget)
        self.setLayout(layout)

if __name__ == '__main__':

    app = QApplication(sys.argv)
    form = Form()
    form.show()
    exit(app.exec_())

使用 SortFilterProxy 适用于此示例,但在较大的数据帧上速度非常慢。

上面的代码示例对于非数据帧数据确实有效,读取已排序。创建仅包含元组列表的 model/view 效果很好。

我找到的建议主要有两个方向:记得给视图发信号或使用sortfilterproxy。我记得并尝试过但到目前为止没有成功。似乎与数据框的使用有关。欢迎所有建议。提前致谢。

在下一部分中,我将展示您的印象结果,其中我们看到它被重新排序,而且索引也被重新排序,这导致在未更新时发生变化。

sort clicked col 0 order 1
date
         date      time string         nan                  bla
0  23-02-1978  19:03:13     eh        None  even more some data
2  23-02-1978  19:02:55     he  some data   even more some data
1  23-02-1978  19:01:45     ss  some data   even more some data
sort clicked col 0 order 0
date
         date      time string         nan                  bla
1  23-02-1978  19:01:45     ss  some data   even more some data
2  23-02-1978  19:02:55     he  some data   even more some data
0  23-02-1978  19:03:13     eh        None  even more some data

要更新数据,您必须使用 reset_index() 重置索引。

def sort(self, column, order):
    """Sort table by given column number.
    """
    print('sort clicked col {} order {}'.format(column, order))
    self.layoutAboutToBeChanged.emit()
    print(self._df.columns[column])
    self._df.sort_values('time', ascending=order == Qt.AscendingOrder, inplace=True)
    self._df.reset_index(inplace=True, drop=True) # <-- this is the change
    print(self._df)
    self.layoutChanged.emit()