PyQt 将图像从 URL 加载到 QPixmap 会导致冻结和崩溃 [英] PyQt loading images from URL to QPixmap causes freezes and crash

查看:46
本文介绍了PyQt 将图像从 URL 加载到 QPixmap 会导致冻结和崩溃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我制作了一个应用程序,该应用程序从网站收集图像的 URL 并将它们逐个显示在显示器上.但是,当您使用 QComboBox 滚动浏览图像时,程序会冻结 2-3 秒,这很烦人,因为要滚动浏览的 URL 超过 100 个.如果您尝试快速滚动,应用程序会崩溃.图片甚至不是那么大(小于 300KB),互联网连接已经足够了.有什么解决方案可以解决这个问题吗?

I made an app that collects URLs of images from a website and displays them on display one by one. However, when you scroll through images with QComboBox, the program freezes for 2-3 seconds which is annoying, considering there are more than 100 URLs to scroll through. If you try scrolling fast the app crashes. The pictures are not even that big (less than 300KB), internet connection is good enough. Is there any solution to fix this?

这是代码片段:

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import urllib.request
import sys

class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self.setWindowTitle(" ")
        lo2 = QVBoxLayout()




        self.pixmap = QPixmap()

        self.widgetIMG = QLabel()
        self.widgetIMG.setAlignment(Qt.AlignHCenter)

        self.widgetList = QComboBox()
        self.widgetList.addItems(['first image','second image', 'third image'])
        self.widgetList.currentIndexChanged.connect(self.Display)
        self.url_list = ['http://cards.hearthcards.net/3062325e.png', 'http://cards.hearthcards.net/4fc517c5.png', 'http://cards.hearthcards.net/6c9f07e2.png']


        lo2.addWidget(self.widgetIMG)
        lo2.addWidget(self.widgetList)
        widget = QWidget()             
        widget.setLayout(lo2)  
        self.setCentralWidget(widget)

    def Display(self, id):
        print(id)
        URL = self.url_list[id]
        img = urllib.request.urlopen(URL).read()

        self.pixmap.loadFromData(img)
        self.widgetIMG.setPixmap(self.pixmap.scaledToHeight(380))


if __name__ == '__main__':

    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

也许有一种方法可以在启动时预加载所有图像并以某种方式临时缓存这些图像?最佳解决方案是什么?

Maybe there is a way to preload all images on start and cache those temporarily somehow? What would be the most optimal solution?

推荐答案

问题是urllib.request.urlopen()函数阻塞导致窗口死机,解决方法是执行该任务在另一个线程中,并通过信号将信息发送到主线程.

The problem is that the urllib.request.urlopen() function is blocking causing the window to freeze, the solution is to execute that task in another thread and send the information through signals to the main thread.

from functools import partial
import sys
import urllib.request


from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, Qt, QThread, QTimer
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import (
    QApplication,
    QComboBox,
    QLabel,
    QMainWindow,
    QVBoxLayout,
    QWidget,
)


class Downloader(QObject):
    resultsChanged = pyqtSignal(bytes)

    @pyqtSlot(str)
    def download(self, url):
        img = urllib.request.urlopen(url).read()
        self.resultsChanged.emit(img)


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("")

        self.thread = QThread(self)
        self.thread.start()

        self.downloader = Downloader()
        self.downloader.moveToThread(self.thread)
        self.downloader.resultsChanged.connect(self.on_resultsChanged)

        self.widgetIMG = QLabel(alignment=Qt.AlignHCenter)

        self.widgetList = QComboBox()
        self.widgetList.currentIndexChanged.connect(self.display)
        for text, url in zip(
            ("first image", "second image", "third image"),
            (
                "http://cards.hearthcards.net/3062325e.png",
                "http://cards.hearthcards.net/4fc517c5.png",
                "http://cards.hearthcards.net/6c9f07e2.png",
            ),
        ):
            self.widgetList.addItem(text, url)

        widget = QWidget()
        lo2 = QVBoxLayout(widget)
        lo2.addWidget(self.widgetIMG)
        lo2.addWidget(self.widgetList)
        self.setCentralWidget(widget)

    @pyqtSlot(int)
    def display(self, ix):
        url = self.widgetList.itemData(ix)
        wrapper = partial(self.downloader.download, url)
        QTimer.singleShot(0, wrapper)

    @pyqtSlot(bytes)
    def on_resultsChanged(self, img):
        pixmap = QPixmap()
        pixmap.loadFromData(img)
        self.widgetIMG.setPixmap(pixmap.scaledToHeight(380))

    def closeEvent(self, event):
        self.thread.quit()
        self.thread.wait()
        super().closeEvent(event)


if __name__ == "__main__":

    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

另一种可能的解决方案是使用 Qt Network:

Another possible solution is to use Qt Network:

import sys


from PyQt5.QtCore import pyqtSlot, Qt, QUrl
from PyQt5.QtGui import QPixmap
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QNetworkRequest
from PyQt5.QtWidgets import (
    QApplication,
    QComboBox,
    QLabel,
    QMainWindow,
    QVBoxLayout,
    QWidget,
)


class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setWindowTitle("")

        self.manager = QNetworkAccessManager()
        self.manager.finished.connect(self.on_finished)

        self.widgetIMG = QLabel(alignment=Qt.AlignHCenter)

        self.widgetList = QComboBox()
        self.widgetList.currentIndexChanged.connect(self.display)
        for text, url in zip(
            ("first image", "second image", "third image"),
            (
                "http://cards.hearthcards.net/3062325e.png",
                "http://cards.hearthcards.net/4fc517c5.png",
                "http://cards.hearthcards.net/6c9f07e2.png",
            ),
        ):
            self.widgetList.addItem(text, url)

        widget = QWidget()
        lo2 = QVBoxLayout(widget)
        lo2.addWidget(self.widgetIMG)
        lo2.addWidget(self.widgetList)
        self.setCentralWidget(widget)

    @pyqtSlot(int)
    def display(self, ix):
        url = self.widgetList.itemData(ix)
        self.start_request(url)

    def start_request(self, url):
        request = QNetworkRequest(QUrl(url))
        self.manager.get(request)

    @pyqtSlot(QNetworkReply)
    def on_finished(self, reply):
        target = reply.attribute(QNetworkRequest.RedirectionTargetAttribute)
        if reply.error():
            print("error: {}".format(reply.errorString()))
            return
        elif target:
            newUrl = reply.url().resolved(target)
            self.start_request(newUrl)
            return
        pixmap = QPixmap()
        pixmap.loadFromData(reply.readAll())
        self.widgetIMG.setPixmap(pixmap.scaledToHeight(380))


if __name__ == "__main__":

    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

总的来说,我更喜欢第二种解决方案(Qt 风格的解决方案),因为它通过消除应用程序的复杂性来避免使用线程,因为 Qt Network 使用事件循环并且不会阻塞它.

In general I prefer the second solution(The Qt-style solution) since it avoids using threads by removing complexity from the application since Qt Network uses the event loop and does not block it.

这篇关于PyQt 将图像从 URL 加载到 QPixmap 会导致冻结和崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆