处理 QListWidget 中的数千个项目并减少延迟
Handling thousands of items in a QListWidget and reducing lag
我有 QListWidget 和一本字典,我正在使用 for 循环遍历它们。 for 循环的每次迭代都会将一个项目添加到 QListWidget 中,同时为每个项目添加一些按钮和标签。一切正常,但我遇到的问题是每次刷新列表时列表都需要很长时间(1k 项大约需要 20 秒)才能加载。在此期间,GUI 完全没有响应(只要不会花费太长时间,我就可以接受)。我发现的一个解决方案是,如果我在执行迭代时隐藏 (self.hide()
) QMainWindow,然后在它完成后显示 (self.show()
) 它(大约 1.5 秒),刷新时间会大大减少对于 1k 项),所以我假设这是资源问题。是否有可能获得大约 1.5 秒的刷新时间,同时仍然保持 GUI 可见(并且无响应),就像冻结 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_())
我有 QListWidget 和一本字典,我正在使用 for 循环遍历它们。 for 循环的每次迭代都会将一个项目添加到 QListWidget 中,同时为每个项目添加一些按钮和标签。一切正常,但我遇到的问题是每次刷新列表时列表都需要很长时间(1k 项大约需要 20 秒)才能加载。在此期间,GUI 完全没有响应(只要不会花费太长时间,我就可以接受)。我发现的一个解决方案是,如果我在执行迭代时隐藏 (self.hide()
) QMainWindow,然后在它完成后显示 (self.show()
) 它(大约 1.5 秒),刷新时间会大大减少对于 1k 项),所以我假设这是资源问题。是否有可能获得大约 1.5 秒的刷新时间,同时仍然保持 GUI 可见(并且无响应),就像冻结 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_())