0

I have a small test script shown below, written in Python to demonstrate my point. In summary, I have a QAbstractTableModel implementation which defines canFetchMore and fetchMore in order limit the number of rows that the QTableView loads at once.

This code works great, however I also have setView function which I've defined that filters the QTableView. My problem is, whenever I hide rows from the QTableView, it does not trigger the fetchMore method in my model - so what ends up happening is I get a very blank looking table until I scroll it, triggering the fetchMore method.

What can I do to ensure that both the 'lazy loading' AND filtering capabilities of this program work properly? My end-use case is on a much larger data set, so having both functionalities is very important.

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

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

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

        self.inputBox = QtGui.QLineEdit()
        self.tableView = QtGui.QTableView()

        layout.addWidget(self.inputBox)
        layout.addWidget(self.tableView)

        self.inputBox.textChanged.connect(self._textChanged)
        self.show()

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

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

    def setData(self, data):
        self.model = PandasModel(data)
        self.tableView.setModel(self.model)

    def setView(self, regexp):
        rows = self.model.getRows(regexp)
        for row in range(self.model.totRows):
            if row in rows:
                self.tableView.setRowHidden(row, False)
            else:
                self.tableView.setRowHidden(row, True)

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, parent=None):
        QtCore.QAbstractTableModel.__init__(self, parent)
        self._data = data
        self.curRows = 0
        self.totRows = len(self._data.values)

    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}
    data = pd.DataFrame(data)
    myApp.setData(data)
    myApp.startup()
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
wesanyer
  • 982
  • 1
  • 6
  • 27
  • It might be a Qt bug. The workaround is to implement a filtering proxy. – Kuba hasn't forgotten Monica Feb 12 '16 at 21:19
  • Very well may be a bug, or perhaps I am not firing some sort of signal that I should fire when I hide the row. Nonetheless, a proxy filter looks very neat and seem the appropriate way to approach this problem, thanks. – wesanyer Feb 12 '16 at 21:29

0 Answers0