I have a simple QListview that displays a list of items Names. I would like to display the thumbnail of each item once it has been downloaded. How can i do the following as I'm new to using something like a background worker and I'm not sure how to achieve this.
This explains what i think would be the best approach...
Use a custom QStyledItemDelegate that overrides the initStyleOption() function.
Detects the lack of an icon and issues an asynchronous request to load it.
In the meantime, display default empty icon so user sees placeholder
When the asynchronous request to download the icon is done, it signals my widget which updates the items icon.
When i create all of my QStandardModelItems, I give them a custom piece of data (a custom role) that holds the path of the thumbnail for each item
import os
import sys
from PySide2 import QtCore, QtGui, QtWidgets
try:
# python 2
from urllib import urlretrieve
from urllib2 import urlopen
except Exception as e:
# python 3
from urllib.request import urlretrieve, urlopen
import time
from urllib.parse import urlparse
def getThumbnail(url, output):
if os.path.exists(output):
return output
# # download 1
# # urlretrieve(url, output)
# # return os.path.abspath(output)
# download 2
response = urlopen(url, timeout=5000)
f = open(output, "wb")
try:
f.write(response.read())
finally:
f.close()
return output
class ExampleDialog(QtWidgets.QDialog):
def __init__(self):
super(ExampleDialog, self).__init__()
self.itemModel = QtGui.QStandardItemModel()
self.uiListView = QtWidgets.QListView()
# self.uiListView.setViewMode(QtWidgets.QListView.IconMode)
self.uiListView.setIconSize(QtCore.QSize(80, 60)) #set icon size
self.uiListView.setGridSize(QtCore.QSize(90, 70)) #set icon grid display
self.uiListView.setModel(self.itemModel)
self.mainLayout = QtWidgets.QVBoxLayout(self)
self.mainLayout.addWidget(self.uiListView)
self.populateImages()
def populateImages(self):
root = os.path.join(os.getenv('APPDATA'), 'MyApp\\cache')
if not os.path.exists(root):
os.makedirs(root)
print('IMAGES:', root)
for x in range(20):
url = 'https://picsum.photos/id/{}/80/60.jpg'.format(x)
p = urlparse(url).path
ext = os.path.splitext(p)[-1]
output = os.path.join(root, '{}{}'.format(x, ext))
# get thumbnail
getThumbnail(url, output)
# Item
item = QtGui.QStandardItem('{}'.format(x))
item.setData(QtGui.QPixmap(output), QtCore.Qt.DecorationRole)
item.setData(output, QtCore.Qt.UserRole)
self.itemModel.appendRow(item)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = ExampleDialog()
window.show()
window.raise_()
sys.exit(app.exec_())
Instead of using a background worker you can use QNetworkAccessManager for asynchronous downloading.
from dataclasses import dataclass
from functools import cached_property
import sys
from PySide2 import QtCore, QtGui, QtWidgets, QtNetwork
@dataclass
class IconDownloader(QtCore.QObject):
url: QtCore.QUrl
index: QtCore.QPersistentModelIndex
_parent: QtCore.QObject = None
def __post_init__(self):
super().__init__()
self.setParent(self._parent)
@cached_property
def network_manager(self):
manager = QtNetwork.QNetworkAccessManager()
manager.finished.connect(self._handle_finished)
return manager
def start(self):
if self.index.isValid():
request = QtNetwork.QNetworkRequest(self.url)
request.setAttribute(
QtNetwork.QNetworkRequest.FollowRedirectsAttribute, True
)
self.network_manager.get(request)
def _handle_finished(self, reply):
if reply.error() == QtNetwork.QNetworkReply.NoError:
pixmap = QtGui.QPixmap()
ok = pixmap.loadFromData(reply.readAll())
if ok and self.index.isValid():
model = self.index.model()
model.setData(
QtCore.QModelIndex(self.index), pixmap, QtCore.Qt.DecorationRole
)
else:
print(reply.error(), reply.errorString())
reply.deleteLater()
self.deleteLater()
class ExampleDialog(QtWidgets.QDialog):
def __init__(self):
super(ExampleDialog, self).__init__()
self.itemModel = QtGui.QStandardItemModel()
self.uiListView = QtWidgets.QListView()
# self.uiListView.setViewMode(QtWidgets.QListView.IconMode)
self.uiListView.setIconSize(QtCore.QSize(80, 60)) # set icon size
self.uiListView.setGridSize(QtCore.QSize(90, 70)) # set icon grid display
self.uiListView.setModel(self.itemModel)
self.mainLayout = QtWidgets.QVBoxLayout(self)
self.mainLayout.addWidget(self.uiListView)
self.populateImages()
def populateImages(self):
for x in range(20):
url = f"https://picsum.photos/id/{x}/80/60.jpg"
item = QtGui.QStandardItem(f"x")
self.itemModel.appendRow(item)
downloader = IconDownloader(
QtCore.QUrl(url),
QtCore.QPersistentModelIndex(self.itemModel.indexFromItem(item)),
self,
)
downloader.start()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = ExampleDialog()
window.show()
window.raise_()
sys.exit(app.exec_())