Я пытаюсь загрузить большой набор изображений, несколько тысяч или более, в QListView, используя QAbstractListModel. Я хотел воспользоваться загрузкой их асинхронно, если это возможно.
Для простоты сейчас в моей модели есть метод LoadImages, который можно заменить на любой каталог изображений для тестирования.
- QStandardItem ( DisplayRole ): имя изображения
- QStandardItem ( UserRole ): полный путь к файлу изображения
Моя цель - сначала загрузить изображение заполнителя для всех элементов списка, а затем в отдельном потоке загрузить миниатюру этого конкретного элемента с диска. После загрузки он должен обновить графический интерфейс для загруженного изображения. Оригинальный код использовал QStyledItemDelegate для обработки кэша всех изображений, которые необходимо загрузить. Если изображение отсутствует в кэше, оно рисует изображение-заполнитель и отправляет сигнал другому потоку, который загружает это изображение и помещает его в кэш.
Мой код в значительной степени основан на этом посте здесь: Как правильно загружать изображения асинхронно в PyQt5? , который не предоставляет полный код.
Кажется, что загружается изображение заполнителя правильно, но оно не будет загружать изображение с диска. Я продолжаю получать эти ошибки:
QPixmap::scaled: Pixmap is a null pixmap
QPixmap: It is not safe to use pixmaps outside the GUI thread
В оригинальном потоке на Stackoverflow сказано, что есть решение, которое, я думаю, я реализовал правильно. Я немного растерялся, почему это не сработало.
import os
import sys
from PySide.QtCore import *
from PySide.QtGui import *
PLACEHOLDER_IMAGE_PATH = "C:/Users/jmartini/Desktop/Trash/imageIcon.svg"
IMAGES_PATH = "C:/Users/jmartini/Desktop/Trash/imagesList"
class MyDelegate(QStyledItemDelegate):
t1 = Signal(str, str, dict)
def __init__(self, image_cache, loader_thread, parent=None):
super(MyDelegate, self).__init__(parent)
self.placeholder_image = QIcon(PLACEHOLDER_IMAGE_PATH).pixmap(QSize(128,128))
self.image_cache = image_cache
self.loader_thread = loader_thread
self.t1.connect(self.loader_thread.insert_into_queue)
def paint(self, QPainter, QStyleOptionViewItem, QModelIndex):
rect = QStyleOptionViewItem.rect
asset_name = QModelIndex.data(Qt.DisplayRole)
asset_thumb = QModelIndex.data(Qt.UserRole)
pic_rect = QRect(rect.left(), rect.top(), 128, 128)
text_rect = QRect(rect.left(), rect.top() + 128, 128, 22)
try:
cached_thumb = self.image_cache[asset_name]
print("Got image: {} from cache".format(asset_name))
except KeyError as e:
self.t1.emit(asset_name, asset_thumb, self.image_cache)
cached_thumb = self.placeholder_image
print("Drawing placeholder image for {}".format(asset_name))
QPainter.drawPixmap(pic_rect, cached_thumb)
QPainter.drawText(text_rect, Qt.AlignCenter, asset_name)
if QStyleOptionViewItem.state & QStyle.State_Selected:
highlight_color = QStyleOptionViewItem.palette.highlight().color()
highlight_color.setAlpha(50)
highlight_brush = QBrush(highlight_color)
QPainter.fillRect(rect, highlight_brush)
def sizeHint(self, QStyleOptionViewItem, QModelIndex):
return QSize(128, 150)
class MyModel(QAbstractListModel):
def __init__(self, *args, **kwargs):
QAbstractListModel.__init__(self, *args, **kwargs)
self._items = []
def rowCount(self, index=QModelIndex()):
return len(self._items)
def itemByIndex(self, index):
if (index < 0 or index >= len(self._items)):
return None
return self._items[index]
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None
item = self.itemByIndex(index.row())
if not item:
return None
elif role == Qt.DisplayRole:
return item.data(role=Qt.DisplayRole)
elif role == Qt.DecorationRole:
return item.data(role=Qt.DecorationRole)
elif role == Qt.UserRole:
return item.data(role=Qt.UserRole)
return None
# Extra Methods
def loadImages(self):
self.image_dir = IMAGES_PATH
for img in os.listdir(self.image_dir):
filepath = os.path.join(self.image_dir, img)
item = QStandardItem(filepath)
item.setData(os.path.basename(filepath), role=Qt.DisplayRole)
item.setData(filepath, role=Qt.UserRole)
self.appendItem(item)
def appendItem(self, item):
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self._items.append(item)
self.endInsertRows()
class LoaderThread(QObject):
def __init__(self):
super(LoaderThread, self).__init__()
@Slot(str, str, dict)
def insert_into_queue(self, name, thumb_path, image_cache):
print("Got signal, loading image for {} from disk".format(name))
image = QImage(thumb_path)
pixmap = QPixmap.fromImage(image).scaled(128, 128)
image_cache[name] = pixmap
print("Image for {} inserted to cache".format(name))
class Example(QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.resize(960, 800)
self.setWindowTitle('Image Viewer')
image_cache = {}
lt = LoaderThread()
self.thread = QThread()
lt.moveToThread(self.thread)
self.thread.start()
self.delegate = MyDelegate(image_cache, lt)
self.model = MyModel()
self.proxyModel = QSortFilterProxyModel()
self.proxyModel.setSortCaseSensitivity(Qt.CaseInsensitive)
self.proxyModel.setSourceModel(self.model)
self.list = QListView()
self.list.setViewMode(QListView.IconMode)
self.list.setResizeMode(QListView.Adjust)
self.list.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.list.setModel(self.proxyModel)
self.list.setItemDelegate(self.delegate)
self.model.loadImages()
# Layout
layout = QVBoxLayout()
layout.addWidget(self.list)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
def main():
app = QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()