QListView中自定义IndexWidget的平滑懒加载与卸载
我正在写一个应用程序,里面用自定义的QWidget来替代PyQt中的普通列表项或代理。我参考了在QListView的QWidgetDelegate的paint()方法中渲染QWidget等答案,来实现一个带有自定义小部件的QTableModel。下面的问题是我在实现过程中遇到的一些困难,我不知道该怎么解决:
- 当某些项目不显示时,如何卸载它们。我打算为一个有成千上万条目的列表构建我的应用程序,而我不能在内存中保留那么多小部件。
- 如何加载那些还没有在视图中的项目,或者至少是异步加载它们。小部件渲染需要一点时间,下面的示例代码在滚动列表时明显有延迟。
- 在下面的实现中,当我滚动列表时,每次加载的新按钮在加载时会在QListView的左上角闪现一下,然后才会跳到正确的位置。怎么才能避免这种情况呢?
--
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()
2 个回答
1
QTableView
只会向模型请求视口内的数据显示,所以你的数据量大小其实对速度影响不大。既然你已经创建了一个 QAbstractListModel
的子类,你可以重新实现它,让它在初始化时只返回少量的行数据,并修改它的 canFetchMore
方法,当总记录数还没显示完时返回 True
。不过,考虑到你的数据量,可能需要考虑创建一个数据库,并使用 QSqlQueryModel
或 QSqlTableModel
,这两个模型都支持分批加载,每次加载256条数据。
为了让加载项目更顺畅,你可以连接到 valueChanged
信号,来自你的 QTableView.verticalScrollBar()
,根据它的 value
和 maximum
之间的差值来做一些处理,比如:
while xCondition:
if self.model.canFetchMore():
self.model.fetchMore()
使用 setIndexWidget
会显著降低你的应用程序速度。你可以使用 QItemDelegate
,并自定义它的 paint
方法来显示一个按钮,像这样:
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()
然后用以下方式进行设置:
myView.setItemDelegateForColumn(columnNumber, myItemDelegate)
3
你可以使用一个代理模型来避免加载所有的控件。这个代理模型可以根据视口的高度和控件的高度来计算行数。它还可以根据滚动条的值来计算项目的索引。
这个方法可能不太稳定,但应该能奏效。
如果你修改你的 data() 方法如下:
button = QtGui.QPushButton("This is item #%s" % index.row())
self.list.setIndexWidget(index, button)
button.setVisible(False)
那么这些项目在被移动到它们的位置之前是不会显示的(对我来说是有效的)。