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