Smooth lazy loading and unloading of custom IndexWidget in QListView

I am writing an application that uses a custom QWidget instead of the usual listitems or delegates in PyQt. I executed the answer in the Render QWidget in the QWidgetDelegate paint () method for QListView - among other things - implement a QTableModel with custom widgets. The resulting code sample is at the bottom of this question. There are some implementation problems that I don’t know how to solve:

  • Unload items when they are not displayed. I plan to create my application for a list that will contain thousands of entries, and I cannot store this many widgets in memory.
  • Download items that are not yet displayed, or at least load them asynchronously. Widgets take some time to render, and the following code example has some obvious lag when scrolling through the list.
  • When scrolling through the list in the implementation below, each loaded button at boot is displayed in the upper left corner of the QListView for a second before bouncing in position. How can this be avoided?

-

import sys from PyQt4 import QtGui, QtCore from PyQt4.QtCore import Qt class TestListModel(QtCore.QAbstractListModel): def __init__(self, parent=None): QtCore.QAbstractListModel.__init__(self, parent) self.list = parent def rowCount(self, index): return 1000 def data(self, index, role): if role == Qt.DisplayRole: if not self.list.indexWidget(index): button = QtGui.QPushButton("This is item #%s" % index.row()) self.list.setIndexWidget(index, button) return QtCore.QVariant() if role == Qt.SizeHintRole: return QtCore.QSize(100, 50) def columnCount(self, index): pass def main(): app = QtGui.QApplication(sys.argv) window = QtGui.QWidget() list = QtGui.QListView() model = TestListModel(list) list.setModel(model) list.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel) layout = QtGui.QVBoxLayout(window) layout.addWidget(list) window.setLayout(layout) window.show() sys.exit(app.exec_()) if __name__ == '__main__': main() 
+4
source share
2 answers

You can use the proxy model to avoid loading all widgets. The proxy model can calculate the number of rows with the heights of the viewport and widget. It can calculate the index of items with a scrollbar value.

This is a shaky solution, but it should work.

If you change the data () method with

 button = QtGui.QPushButton("This is item #%s" % index.row()) self.list.setIndexWidget(index, button) button.setVisible(False) 

Elements will not be displayed until they are moved in their positions (this works for me).

+2
source

QTableView only requests model-specific data for items in its viewport, so the size of your data doesn’t really affect the speed. Since you already subclassed QAbstractListModel , you can override it to return only a small set of rows when it is initialized and change it canFetchMore return True if the total volume of records is not displayed. Although with the size of your data, you might want to create a database and use QSqlQueryModel or QSqlTableModel , both of them do lazy loading in 256 groups.

To get a smoother loading of elements, you can connect to the valueChanged signal of your QTableView.verticalScrollBar() and depending on the difference between it, value and maximum has something like:

 while xCondition: if self.model.canFetchMore(): self.model.fetchMore() 

Using setIndexWidget slows down your application significantly. You can use QItemDelegate and set its paint to display a button with something like:

 class MyItemDelegate(QtGui.QItemDelegate): def __init__(self, parent=None): super(MyItemDelegate, self).__init__(parent) def paint(self, painter, option, index): text = index.model().data(index, QtCore.Qt.DisplayRole).toString() pushButton = QtGui.QPushButton() pushButton.setText(text) pushButton.setGeometry(option.rect) painter.save() painter.translate(option.rect.x(), option.rect.y()) pushButton.render(painter) painter.restore() 

And configure it with

 myView.setItemDelegateForColumn(columnNumber, myItemDelegate) 
+1
source

All Articles