将项目动态添加到 QListWIdget

Dynamically add Items to a QListWIdget

我有这个功能

def search(self):
    
    #creating a list just ignore
    self.listWidget.clear()
    self.gogo.keywordSetter(self.lineEdit.text())
    self.gogo.linkSetter()
    self.gogo.resultsGetter()
    
    #iterarting through the generated lsit
    for x in self.gogo.resultsContainer:
        self.listWidget.update()
        #print(x[2])
        #x[2] is the url to an image
        url = x[2]
        print(url)
        
        req = Request(url, headers={'User-Agent': 'Mozilla/5.0'})
        webpage = urlopen(req).read()
        
        pixmap = QPixmap()
        pixmap.loadFromData(webpage)
        icon = QIcon(pixmap)
        #x[1] is a string I want to display it's a title basically
        item = QListWidgetItem(icon, x[1])
        size = QSize()
        size.setHeight(100)
        size.setWidth(400)
        item.setSizeHint(size)
        #item.iconSize(QSize(100, 400))
        self.listWidget.addItem(item)

有效,我的问题是它仅在遍历每个项目后才显示所有内容。 我的意思是我可以使用打印语句看到它正在遍历列表并创建项目但没有显示任何项目。 在完全遍历列表后,它们会立即全部显示出来。 这很慢。我知道部分原因是图片下载,我对此无能为力。但是动态添加项目至少会使其更容易忍受。 尝试使用 update() 但它并没有真正起作用。 另一个奇怪的行为是尽管 clear() 是第一条指令,但它不会在调用该函数后立即清除 listWidget,看起来这是由于同一件事导致所有内容同时显示。

UI 系统使用“事件循环”,负责“绘制”元素并允许用户交互;该循环必须自由执行其工作,并且在其中运行的函数必须尽快 return 以防止 UI 的“冻结”:window 未刷新或正确显示,但似乎对键盘或鼠标事件无响应。

您的函数正是这样做的:它会阻塞所有内容,直到它完成。
调用 update() 是不够的,因为它仅 安排 重绘,这只会在主循环能够处理事件时立即发生。这就是为什么应该始终避免长时间或可能无限的 for/while 循环,就像 time.sleep 这样的任何阻塞函数一样,即使是很短的时间。

"I can do nothing about it."

事实上,不仅你可以,而且你必须

一种可能性是使用线程,特别是 QThread,这是一个接口,允许在单独的线程中执行调用,同时提供 Qt 的 signal/slot 机制,可以在线程之间异步工作。使用 QThread 信号与主线程通信非常重要,因为 UI 元素 不是 线程安全的,并且必须 从不从外部线程访问(或创建)

class Downloader(QThread):
    imageDownloaded = pyqtSignal(int, object)
    def __init__(self, parent, urlList):
        super().__init__(parent)
        self.urlList = urlList

    def run(self):
        for i, url in enumerate(self.urlList):
            req = Request(url, headers={'User-Agent': 'Mozilla/5.0'})
            webpage = urlopen(req).read()
            self.imageDownloaded.emit(i, webpage)


class YourWidget(QWidget):
    # ...
    def search(self):
        self.listWidget.clear()
        self.gogo.keywordSetter(self.lineEdit.text())
        self.gogo.linkSetter()
        self.gogo.resultsGetter()
        
        #iterating through the generated list
        urlList = []
        for x in self.gogo.resultsContainer:
            url = x[2]
            urlList.append(url)
            
            item = QListWidgetItem(x[1])
            item.setSizeHint(QSize(400, 100))
            self.listWidget.addItem(item)

        # the parent argument is mandatory, otherwise there won't be any
        # persistent reference to the downloader, and it will be deleted
        # as soon as this function returns; connecting the finished signal
        # to deleteLater will delete the object when it will be completed.
        downloadThread = Downloader(self, urlList)
        downloadThread.imageDownloaded.connect(self.updateImage)
        downloadThread.finished.connect(downloadThread.deleteLater)
        downloadThread.start()

    def updateImage(self, index, data):
        pixmap = QPixmap()
        if not pixmap.loadFromData(data) or index >= self.listWidget.count():
            return
        self.listWidget.item(index).setIcon(QIcon(pixmap))

注意:上面的代码未经测试,因为不清楚您实际用于下载的模块是什么,也不清楚 self.gogo 是什么。

与上述略有不同的替代方法是使用持久的“下载管理器”线程,queue 请求使用 python Queue,它将在 run() 实现中读取。


考虑到 Qt 提供了已经可以异步工作的 QtNetwork 模块,并实现了相同的概念。

您必须创建一个 QNetworkAccessManager 实例(一个通常足以满足整个应用程序),然后为每个 url 创建一个 QNetworkRequest,最后用该请求调用管理器的 get(),这将 return 稍后用于检索下载数据的 QNetworkReply。

在下面的示例中,我还为回复设置了一个自定义 属性,这样当收到回复时,我们就会知道要做什么它对应的索引:这比上面所做的更重要,因为 QNetworkAccessManager 可以并行下载,并且下载可以按照与请求不同的顺序完成(例如,如果图像具有不同的大小,或者正在下载来自不同的服务器)。

请注意,索引 必须 设置为 Qt 属性,并且不能(或者最好不应该)设置为 python属性,例如 reply.index = i。这是因为我们在 python 中使用的回复只是实际 Qt 对象的包装器,除非我们保持对该包装器的持久引用(例如,通过将其添加到列表中),否则该属性将迷路了。

from PyQt5.QtNetwork import *

class YourWidget(QWidget):
    def __init__(self):
        # ...
        self.downloadManager = QNetworkAccessManager()
        self.downloadManager.finished.connect(self.updateImage)

    # ...
    def search(self):
        self.listWidget.clear()
        self.gogo.keywordSetter(self.lineEdit.text())
        self.gogo.linkSetter()
        self.gogo.resultsGetter()
        
        for i, x in enumerate(self.gogo.resultsContainer):
            item = QListWidgetItem(x[1])
            item.setSizeHint(QSize(400, 100))
            self.listWidget.addItem(item)

            url = QUrl(x[2])
            request = QNetworkRequest(url)
            request.setHeader(request.UserAgentHeader, 'Mozilla/5.0')
            reply = self.downloadManager.get(request)
            reply.setProperty('index', i)

    def updateImage(self, reply):
        index = reply.property('index')
        if isinstance(index, int):
            pixmap = QPixmap()
            if pixmap.loadFromData(reply.readAll()):
                item = self.listWidget.item(index)
                if item is not None:
                    item.setIcon(QIcon(pixmap))
        reply.deleteLater()