首页 > 解决方案 > 处理 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_())

标签: pythonpyqtpyqt5qlistwidget

解决方案


最初的问题是,当它显示时,每次添加项目(和小部件)时,它都会重新绘制所有内容,这与隐藏它并在只有一幅画的地方显示它的任务不同。

另一种不减少加载时间但确实使 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_())

推荐阅读