【问题标题】:PyQt loading images from URL to QPixmap causes freezes and crashPyQt 将图像从 URL 加载到 QPixmap 会导致冻结和崩溃
【发布时间】:2020-04-19 14:05:58
【问题描述】:

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

这是代码的sn-p:

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_())

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

【问题讨论】:

    标签: pyqt pyqt5 freeze qlabel qpixmap


    【解决方案1】:

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

    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:

    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 使用事件循环并且不会阻塞它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-25
      • 1970-01-01
      • 2011-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多