My tool loops through a few thousand images and loads thumbnails of the images into my listview as seen below. However my application freezes until all the images are processed. I was wondering if there was a way i could populate the listview with images as they have been loaded, preventing the UI from freezing and letting the user go about using the application while the images are populated?
This is all done us python and pyside.
Code:
import os
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.resize(800, 400)
self.setWindowTitle('Image Viewer')
self.image_dir = "C:/Users/JokerMartini-Asus/Desktop/textures/thumbs"
self.ui_image_viewer = QtGui.QListView()
self.ui_image_viewer.setViewMode(QtGui.QListView.IconMode)
self.ui_image_viewer.setResizeMode(QtGui.QListView.Adjust)
self.ui_image_viewer.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.ui_image_viewer.setIconSize(QtCore.QSize(300,300))
self.ui_image_viewer.setMovement(QtGui.QListView.Static)
self.ui_image_viewer.setModel(QtGui.QStandardItemModel())
for img in os.listdir(self.image_dir):
img_path = os.path.join(self.image_dir, img)
pixmap = QtGui.QPixmap(img_path)
name = os.path.splitext(os.path.basename(img_path))[0]
item = QtGui.QStandardItem(QtGui.QIcon(pixmap), name)
self.ui_image_viewer.model().appendRow(item)
# Layout
grid = QtGui.QVBoxLayout()
grid.setContentsMargins(10,10,10,10)
grid.addWidget(self.ui_image_viewer)
main_widget = QtGui.QWidget()
main_widget.setLayout(grid)
self.setCentralWidget(main_widget)
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The process that generates an overload, is the creation of the QPixmap, so if we distribute that load over time we can improve the response, we can do this through a QTimer.
import os
import sys
from PySide import QtGui, QtCore
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.resize(800, 400)
self.setWindowTitle('Image Viewer')
self.image_dir = "C:/Users/JokerMartini-Asus/Desktop/textures/thumbs"
self.ui_image_viewer = QtGui.QListView()
self.ui_image_viewer.setViewMode(QtGui.QListView.IconMode)
self.ui_image_viewer.setResizeMode(QtGui.QListView.Adjust)
self.ui_image_viewer.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.ui_image_viewer.setIconSize(QtCore.QSize(300,300))
self.ui_image_viewer.setMovement(QtGui.QListView.Static)
self.ui_image_viewer.setModel(QtGui.QStandardItemModel())
self.imgs = iter(os.listdir(self.image_dir))
timer = QtCore.QTimer(self)
timer.timeout.connect(self.load)
timer.start(100)
# Layout
grid = QtGui.QVBoxLayout()
grid.setContentsMargins(10,10,10,10)
grid.addWidget(self.ui_image_viewer)
main_widget = QtGui.QWidget()
main_widget.setLayout(grid)
self.setCentralWidget(main_widget)
self.show()
def load(self):
try:
img = next(self.imgs)
img_path = os.path.join(self.image_dir, img)
pixmap = QtGui.QPixmap(img_path)
name = os.path.splitext(os.path.basename(img_path))[0]
item = QtGui.QStandardItem(QtGui.QIcon(pixmap), name)
self.ui_image_viewer.model().appendRow(item)
except StopIteration:
self.sender().stop()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Another solution would be to load the image when necessary, that is to say when the scrollbar is moved, but for that we must overwrite the method data of the model, also we must create a new role to store the path of the image, but analyzing the logic of data for QStandardItemModel I have found that called the SizeHintRole role, and it calls the role DecorationRole, generating the same error, so what we should do is put a predefined size so that it avoids the call to DecorationRole, and thus avoid the overload:
import os
import sys
from PySide.QtCore import *
from PySide.QtGui import *
class StandardItem(QStandardItem):
PathRole = Qt.UserRole +1
def __init__(self, *args, **kwargs):
QStandardItem.__init__(self, *args, **kwargs)
self.path = ""
def setData(self, value, role=Qt.UserRole + 1):
if role == StandardItem.PathRole:
self.path = value
else:
QStandardItem.setData(self, value, role)
# self.emitDataChanged()
def data(self, role=Qt.UserRole+1):
if role == StandardItem.PathRole:
return self.path
return QStandardItem.data(self, role)
def type(self):
return Qt.UserType
class StandardItemModel(QStandardItemModel):
def __init__(self, *args, **kwargs):
QStandardItemModel.__init__(self, *args, **kwargs)
self.setItemPrototype(StandardItem())
def data(self, index, role=Qt.DisplayRole):
if role == Qt.DecorationRole:
it = self.itemFromIndex(index)
value = it.data(Qt.DecorationRole)
if value is None:
path = it.data(StandardItem.PathRole)
value = QIcon(QPixmap(path))
it.setData(value, Qt.DecorationRole)
return value
elif role == Qt.SizeHintRole:
return QSize(300, 250)
else:
return QStandardItemModel.data(self, index, role)
class Example(QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.resize(800, 400)
self.setWindowTitle('Image Viewer')
self.image_dir = "C:/Users/JokerMartini-Asus/Desktop/textures/thumbs"
self.ui_image_viewer = QListView()
self.ui_image_viewer.setViewMode(QListView.IconMode)
self.ui_image_viewer.setResizeMode(QListView.Adjust)
self.ui_image_viewer.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.ui_image_viewer.setIconSize(QSize(300, 300))
self.ui_image_viewer.setMovement(QListView.Static)
self.ui_image_viewer.setModel(StandardItemModel())
for img in os.listdir(self.image_dir):
img_path = os.path.join(self.image_dir, img)
name = os.path.splitext(os.path.basename(img_path))[0]
item = StandardItem(name)
item.setData(img_path)
self.ui_image_viewer.model().appendRow(item)
# Layout
grid = QVBoxLayout()
grid.setContentsMargins(10, 10, 10, 10)
grid.addWidget(self.ui_image_viewer)
main_widget = QWidget()
main_widget.setLayout(grid)
self.setCentralWidget(main_widget)
self.show()
def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()