File Image Browser App with thumbnails

Heads up! You've already completed this tutorial.

Rafael_Lago | 2020-11-10 20:14:45 UTC | #1

Hey there,

I'm trying to develop some image viewer in the same vein as KDE's Gwenview, but with some very specific features for my actual application. I'll be using python 3.8 and PyQt5.

The question is: is there already any widget for browsing files in icon-mode, showing thumbnails of images, or would I have to implement that whole jazz from the scratch?


martin | 2020-11-24 10:28:12 UTC | #2

Hi @Rafael_Lago welcome to the forum, sorry for the delay in replying

Unfortunately, there isn't a ready-to-go widget for this, but it's fairly straightforward to implement. See a quick example below, which is used the model view framework to render thumbnails for image files in a folder. Here we're using a table view to layout the items, and a custom delegate to draw the thumbnails (you can add anything else you like).

The complete guide to packaging Python GUI applications with PyInstaller.
[[ discount.discount_pc ]]% OFF for the next [[ discount.duration ]] [[discount.description ]] with the code [[ discount.coupon_code ]]

Purchasing Power Parity

Developers in [[ country ]] get [[ discount.discount_pc ]]% OFF on all books & courses with code [[ discount.coupon_code ]]

python
import glob
import math
import sys
from collections import namedtuple

from PyQt5.QtCore import QAbstractTableModel, Qt, QSize
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableView, QStyledItemDelegate

# Create a custom namedtuple class to hold our data.
preview = namedtuple("preview", "id title image")

NUMBER_OF_COLUMNS = 4
CELL_PADDING = 20 # all sides

class PreviewDelegate(QStyledItemDelegate):

    def paint(self, painter, option, index):
        # data is our preview object
        data = index.model().data(index, Qt.DisplayRole)
        if data is None:
            return

        width = option.rect.width() - CELL_PADDING * 2
        height = option.rect.height() - CELL_PADDING * 2

        # option.rect holds the area we are painting on the widget (our table cell)
        # scale our pixmap to fit
        scaled = data.image.scaled(
            width,
            height,
            aspectRatioMode=Qt.KeepAspectRatio,
        )
        # Position in the middle of the area.
        x = CELL_PADDING + (width - scaled.width()) / 2
        y = CELL_PADDING + (height - scaled.height()) / 2

        painter.drawImage(option.rect.x() + x, option.rect.y() + y, scaled)

    def sizeHint(self, option, index):
        # All items the same size.
        return QSize(300, 200)


class PreviewModel(QAbstractTableModel):
    def __init__(self, todos=None):
        super().__init__()
        # .data holds our data for display, as a list of Preview objects.
        self.previews = []

    def data(self, index, role):
        try:
            data = self.previews[index.row() * 4 + index.column() ]
        except IndexError:
            # Incomplete last row.
            return

        if role == Qt.DisplayRole:
            return data   # Pass the data to our delegate to draw.

        if role == Qt.ToolTipRole:
            return data.title

    def columnCount(self, index):
        return NUMBER_OF_COLUMNS

    def rowCount(self, index):
        n_items = len(self.previews)
        return math.ceil(n_items / NUMBER_OF_COLUMNS)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.view = QTableView()
        self.view.horizontalHeader().hide()
        self.view.verticalHeader().hide()
        self.view.setGridStyle(Qt.NoPen)

        delegate = PreviewDelegate()
        self.view.setItemDelegate(delegate)
        self.model = PreviewModel()
        self.view.setModel(self.model)

        self.setCentralWidget(self.view)

        # Add a bunch of images.
        for n, fn in enumerate(glob.glob("*.jpg")):
            image = QImage(fn)
            item = preview(n, fn, image)
            self.model.previews.append(item)
        self.model.layoutChanged.emit()

        self.view.resizeRowsToContents()
        self.view.resizeColumnsToContents()


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

If you drop this file in any folder containing jpeg files (extension "*.jpg") they'll be shown in a list.

fileimagebrowser.PNG|690x388


Rafael_Lago | 2020-11-24 10:28:12 UTC | #3

Thank you very much! I was actually trying to develop it using QTableView, but I am not very familiar with this part of Qt5 (yet). Your code is very helpful!


Rafael_Lago | 2020-11-24 10:28:07 UTC | #4

So, I extended the concept, but using QFilesystemModel and QListView in IconMode with a given gridSize:

modUI.py: https://pastebin.com/q5jzz7S8 main.py: https://pastebin.com/Mes6cVZt

One of the remaining problems is that the position of the icons is not updated when I resize the window, which gives some funny results (see attached image).

I assume I have to connect some signal to some slot (i.e. resize of window to QListView.update?) but I'm not very familiar with Qt at this point to know which signal to what. Can anyone shed some light?

ksnip_20201113-163506|690x414

EDIT#1: nevermind, I just found about setResizeMode(QtWidgets.QListView.Adjust)


Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak

File Image Browser App with thumbnails was written by Martin Fitzpatrick .

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt.