如何使用QSortFilterProxy和实现canFetchMore和fetchMore特性的QAbstractTableModel?

2024-04-26 17:57:05 发布

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

下面是我正在使用的示例代码:

from PyQt4 import QtCore, QtGui
import pandas as pd
import re
import sys
import datetime as dt

class MainWindow(QtGui.QWidget):
    def __init__(self):
        self.app = QtGui.QApplication(sys.argv)
        super(MainWindow, self).__init__()

        layout = QtGui.QVBoxLayout()
        top = QtGui.QHBoxLayout()
        self.setLayout(layout)

        self.inputBox = QtGui.QLineEdit()
        self.tableView = QtGui.QTableView()
        self.startDate = QtGui.QDateEdit()
        self.stopDate = QtGui.QDateEdit()
        self.startDate.setCalendarPopup(True)
        self.stopDate.setCalendarPopup(True)
        self.startDate.setDateRange(QtCore.QDate(2016, 1, 1), QtCore.QDate(2016, 1, 31))
        self.stopDate.setDateRange(QtCore.QDate(2016, 1, 1), QtCore.QDate(2016, 1, 31))
        self.startDate.setDate(QtCore.QDate(2016, 1, 1))
        self.stopDate.setDate(QtCore.QDate(2016, 1, 15))

        top.addWidget(self.inputBox)
        top.addWidget(self.startDate)
        top.addWidget(self.stopDate)
        layout.addLayout(top)
        layout.addWidget(self.tableView)

        self.inputBox.textChanged.connect(self._textChanged)
        self.startDate.dateChanged.connect(self._dateChanged)
        self.stopDate.dateChanged.connect(self._dateChanged)
        self.show()

    def startup(self):
        sys.exit(self.app.exec_())

    def _textChanged(self):
        self.setView()

    def _dateChanged(self):
        start = self.startDate.date()
        end = self.stopDate.date()
        self.start = dt.date(year=start.year(), month=start.month(), day=start.day())
        self.stop = dt.date(year=end.year(), month=end.month(), day=end.day())
        #print('self.start = {}, self.stop = {}'.format(self.start, self.stop))
        self.setView()

    def setData(self, data, start, end, pnCol, dateCol):
        self.start = start
        self.stop = end

        self.model = PandasModel(data, pnCol, dateCol)
        self.proxyModel = MyProxyModel(start, end)
        self.proxyModel.setSourceModel(self.model)
        self.tableView.setModel(self.proxyModel)

    def setView(self):
        text = self.inputBox.text()
        pattern = "^" + text + ".*"

        self.proxyModel.setFilterRegExp(QtCore.QRegExp(pattern))
        self.proxyModel.setFilterKeyColumn(0)

        self.proxyModel.setMinDate(self.start)
        self.proxyModel.setMaxDate(self.stop)

class MyProxyModel(QtGui.QSortFilterProxyModel):
    def __init__(self, minDate, maxDate):
        super(MyProxyModel, self).__init__()

        self.minDate = minDate
        self.maxDate = maxDate

    def filterAcceptsRow(self, row, parent):
        #if self.sourceModel().rowCount() > 0:
            #QtCore.pyqtRemoveInputHook()
            #import pdb; pdb.set_trace()
        pnIndex = self.sourceModel().index(row, self.sourceModel().pnCol, parent)
        dateIndex = self.sourceModel().index(row, self.sourceModel().dateCol, parent)

        if not (pnIndex.isValid() and dateIndex.isValid()):
            return False
        else:
            text = self.sourceModel().data(pnIndex)
            match = re.match(self.filterRegExp().pattern(), text)
            goodPN = False
            if match:
                goodPN = True
            goodDate = self.isValidDate(self.sourceModel().data(dateIndex))

            return goodPN and goodDate

    def setMinDate(self, date):
        self.minDate = date

    def setMaxDate(self, date):
        self.maxDate = date

    def isValidDate(self, date):
        year, month, day = date.split('-')
        date = dt.date(year=int(year), month=int(month), day=int(day))
        valid = self.minDate <= date <= self.maxDate
        #print('min = {}, max = {}, test = {}, valid = {}'.format(self.minDate, self.maxDate, date, valid))
        return valid

class PandasModel(QtCore.QAbstractTableModel):
    """
    Class to populate a table view with a pandas dataframe
    Stolen from http://stackoverflow.com/questions/31475965/fastest-way-to-populate-qtableview-from-pandas-data-frame
    """
    def __init__(self, data, pnCol, dateCol, parent=None):
        QtCore.QAbstractTableModel.__init__(self, parent)
        self._data = data
        self.curRows = 0
        self.totRows = len(self._data.values)
        self.pnCol = pnCol
        self.dateCol = dateCol

    def canFetchMore(self, index):
        """
        Note this and my fetchMore implementation were stolen from
        https://riverbankcomputing.com/pipermail/pyqt/2009-May/022968.html
        """
        if self.curRows < self.totRows:
            return True
        else:
            return False

    def fetchMore(self, index):
        remainder = self.totRows - self.curRows
        itemsToFetch = min(5, remainder)

        self.beginInsertRows(QtCore.QModelIndex(), self.curRows, self.curRows+(itemsToFetch-1))
        self.curRows += itemsToFetch
        self.endInsertRows()

    def rowCount(self, parent=None):
        return self.curRows

    def columnCount(self, parent=None):
        return self._data.columns.size

    def data(self, index, role=QtCore.Qt.DisplayRole):  
        if index.isValid():
            if role == QtCore.Qt.DisplayRole:
                #QtCore.pyqtRemoveInputHook()
                return str(self._data.iloc[index.row(), index.column()])
        return None

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

    def getRows(self, regexp):
        out = []
        col = self._data.columns.get_loc('_data')
        for row in range(self.rowCount()):
            check = self.data(self.index(row, col))
            match = re.match(regexp, check)
            if match:
                out.append(row)
        return out

if __name__ == "__main__":
    myApp = MainWindow()

    data = {'a':range(100),
            'b':[str(chr(i+97))for i in range(10)]*10,
            '_data':['abc', 'acd', 'ade', 'bcd', 'bde', 'bef', 'cde', 'cef', 'cfg', 'def']*10,
            'c':['123', '456', '789', '101', '102', '103', '104', '105', '106', '107']*10,
            '_dates':[dt.date(year=2016, month=1, day=i) for i in range(1, 20, 2)]*10}
    data = pd.DataFrame(data)
    start = dt.date(year=2016, month=1, day=1)
    stop = dt.date(year=2016, month=1, day=15)
    pnCol = data.columns.get_loc('_data')
    dateCol = data.columns.get_loc('_dates')
    myApp.setData(data, start, stop, pnCol, dateCol)
    myApp.startup()

最终的结果是过滤器按预期工作,但是由于延迟加载,列表在我滚动之前基本上保持为空。你知道吗

我认为这个错误的来源是第132行,在我实现的rowCount中。这里,我返回self.curRows,而不是self.totRows,正如Qt文档中关于如何实现延迟加载的建议here。具体来说,在页面底部附近,"Notice that the row count is only the items we have added so far, i.e., not the number of entries in the directory."

所以,我的问题是如何修改我的方法,以便我可以同时利用延迟加载和过滤?你知道吗

更新:稍微修改了代码以解决一些错误,特别是我不再使用toString(),而是实现了python的re。还修复了一些拼写错误。你知道吗


Tags: selfdatadateindexreturnifdefyear