python - 处理 QListWidget 中的数千个项目并减少延迟
问题描述
我有 QListWidget 和我正在使用 for 循环循环的字典。for 循环的每次迭代都会向 QListWidget 添加一个项目,以及附加到每个项目的几个按钮和标签。一切正常,但每次刷新列表时,我都会遇到列表需要很长时间(1k 个项目大约需要 20 秒)才能加载的问题。在此期间,GUI 完全没有响应(只要不花费太长时间,我就可以接受)。我发现的一个解决方案是,如果我self.hide()
在 QMainWindow 进行迭代时隐藏()QMainWindow,然后显示(self.show()
) 完成后(1k 个项目大约需要 1.5 秒),所以我假设这是资源的问题。是否有可能通过冻结 GUI 来获得大约 1.5 秒的刷新时间,同时仍然保持 GUI 可见(并且无响应),以便在刷新列表时它不会占用尽可能多的资源。
示例代码:
import sys
import time
from PyQt5.QtWidgets import QApplication, QMainWindow, QListWidget, QListWidgetItem, QPushButton, QVBoxLayout, QWidget
class window(QMainWindow):
def __init__(self):
super(window, self).__init__()
self.show()
self.setFixedSize(800, 500)
self.listwidget()
self.refreshlist()
def listwidget(self):
self.list = QListWidget(self)
self.list.setFixedSize(800, 500)
self.list.show()
def refreshlist(self): # uncomment self.hide() and self.show() to see how much faster it is
start = time.time()
# self.hide()
for i in range(1000):
item = QListWidgetItem(str(i))
self.list.addItem(item)
widget = QWidget(self.list)
layout = QVBoxLayout(widget)
layout.addWidget(QPushButton())
self.list.setItemWidget(item, widget)
# self.show()
print(f"took {time.time() - start} seconds")
"""
average time with hiding and showing was 0.3 seconds
average time without hiding and showing was 14 seconds
"""
if __name__ == '__main__':
app = QApplication([])
Gui = window()
sys.exit(app.exec_())
解决方案
最初的问题是,当它显示时,每次添加项目(和小部件)时,它都会重新绘制所有内容,这与隐藏它并在只有一幅画的地方显示它的任务不同。
另一种不减少加载时间但确实使 GUI 可见的替代方法是使用队列和计时器每 T 秒添加项目块。您还可以添加一个 gif,向用户指示正在加载信息。
from collections import deque
from functools import cached_property
import sys
from PyQt5.QtCore import pyqtSignal, QTimer
from PyQt5.QtGui import QMovie
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QLabel,
QListWidget,
QListWidgetItem,
QPushButton,
QStackedWidget,
QVBoxLayout,
QWidget,
)
class ListWidget(QListWidget):
started = pyqtSignal()
finished = pyqtSignal()
CHUNK = 50
INTERVAL = 0
def __init__(self, parent=None):
super().__init__(parent)
self.timer.timeout.connect(self.handle_timeout)
@cached_property
def queue(self):
return deque()
@cached_property
def timer(self):
return QTimer(interval=self.INTERVAL)
def fillData(self, data):
self.started.emit()
self.queue.clear()
self.queue.extend(data)
self.timer.start()
def handle_timeout(self):
for i in range(self.CHUNK):
if self.queue:
value = self.queue.popleft()
self.create_item(str(value))
else:
self.timer.stop()
self.finished.emit()
break
def create_item(self, text):
item = QListWidgetItem(text)
self.addItem(item)
widget = QWidget()
layout = QVBoxLayout(widget)
button = QPushButton(text)
layout.addWidget(button)
layout.setContentsMargins(0, 0, 0, 0)
self.setItemWidget(item, widget)
class Window(QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setFixedSize(800, 500)
self.setCentralWidget(self.stackedWidget)
self.stackedWidget.addWidget(self.gifLabel)
self.stackedWidget.addWidget(self.listWidget)
self.listWidget.started.connect(self.handle_listwidget_started)
self.listWidget.finished.connect(self.handle_listwidget_finished)
@cached_property
def stackedWidget(self):
return QStackedWidget()
@cached_property
def listWidget(self):
return ListWidget()
@cached_property
def gifLabel(self):
label = QLabel(scaledContents=True)
movie = QMovie("loading.gif")
label.setMovie(movie)
return label
def handle_listwidget_started(self):
self.gifLabel.movie().start()
self.stackedWidget.setCurrentIndex(0)
def handle_listwidget_finished(self):
self.gifLabel.movie().stop()
self.stackedWidget.setCurrentIndex(1)
if __name__ == "__main__":
app = QApplication([])
w = Window()
w.show()
w.listWidget.fillData(range(1000))
sys.exit(app.exec_())
推荐阅读
- java - 从 Linux 机器中的 Java 类交互地运行 shell 脚本
- python - 是否会在 python 中使用下面的 for 循环完全读取和写入文件?
- laravel - Laravel:所有 RCPT 命令都被拒绝并出现此错误:503-relay not allowed, authentication required
- php - 它是恶意的 php 代码吗?php.var.function.14
- node.js - 正在显示弃用警告“不推荐使用 server/replset/mongos/db 选项”
- ios - 这种图像查看器是否有任何现有的库?
- java - OSGi bnd gradle 非工作区演示或模板
- react-native - 为什么我在本机反应中收到此错误?
- ios - Swift 将带有参数的闭包作为参数传递
- testing - 即使在服务器关闭后,我的 jmeter 脚本也能成功执行。它没有抛出任何错误并将响应代码提供为 200