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()