2

I'm creating a file browser with PyQt6. This is what I'm thinking of doing right now:

from PyQt6 import QtWidgets as qtw
from PyQt6 import QtGui as qtg


class FileBrowserWidget(qtw.QScrollArea):
  def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)

    self.current_items = []

    self.main_widget = qtw.QWidget()
    self.main_widget.setLayout(qtw.QVBoxLayout())

    self.setWidget(self.main_widget)

  def add_file(self, thumbnail: qtg.QPixmap, description: str):
    item = qtw.QWidget()
    item.setLayout(qtw.QHBoxLayout())
    file_thumbnail_label = qtw.QLabel()
    file_thumbnail_label.setPixmap(thumbnail)
    file_description_label = qtw.QLabel(description)

    item.layout().addWidget(file_thumbnail_label)
    item.layout().addWidget(file_description_label)

    self.current_items.append(item)

Note that this is just a rough sketch of the widget. All the code does is display a (thumbnail, description) pair for files inside a directory in a scrollable window. I also plan to implement pagination for it, with at least 25 rows (files) per page.
My questions are:

  • Is this the way to do it or is there some other better way to go about creating a file browser?
  • How would I go about implementing pagination to the file browser?

EDIT:

  • My apologies, it's not just any file browser, it's an image file browser.
  • Example image of what I'm thinking of creating: enter image description here
オパラ
  • 317
  • 2
  • 10
  • Qt also provides a file dialog, have you checked that? – Jussi Nurminen Apr 27 '22 at 11:20
  • [qtree](https://doc.qt.io/qtforpython-5/PySide2/QtWidgets/QTreeWidget.html) – cards Apr 27 '22 at 11:32
  • @JussiNurminen, and \@cards I don't believe file dialog and qtree are what I'm looking for. I want to create an (image) file browser, not a file picker, and I want to have thumbnail images, not icons. I have updated the post to be a bit more specific about my wants, please have a look. – オパラ Apr 27 '22 at 11:50
  • @cards, please see the above comment, stackoverflow doesn't allow more than one \@s in a comment it seems. – オパラ Apr 27 '22 at 11:52
  • Use QListView with a custom delegate and eventually set the [`viewMode()`](https://doc.qt.io/qt-5/qlistview.html#viewMode-prop) to icon mode. QTreeView is also a viable option. That's how it's implemented in the non native QFileDialog, and in most file browser created with Qt, even when showing thumbnails. – musicamante Apr 27 '22 at 12:24
  • @musicamante, what do you mean by "a custom delegate"? – オパラ Apr 27 '22 at 12:46
  • @オパラ See [delegate classes](https://doc.qt.io/qt-5/model-view-programming.html#delegate-classes). Qt item views use delegates to show and interact with the data model. The default delegate usually shows the text of the index with decorations (colors, icons, etc) and creates an editor (normally, a QLineEdit) when editing the item. Note that you can also use a basic QListWidget and manually set the icon of the item with `QIcon('path_to_image')`. – musicamante Apr 27 '22 at 13:11

1 Answers1

1

A basic possibility is to use a QListWidget, with some customized settings and precautions when adding items:

  • the iconSize() must be big enough to show the thumbnails;
  • a bigger font for the view
  • the sizeHint() of each item must be specified in order to always respect the same row height and provide text elision;
  • the image must be scaled and "enlarged" to the icon size in order to keep vertical alignment of the text, otherwise images that have different widths will show the text starting at different positions;
class ImageListView(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()

        self.view = QtWidgets.QListWidget()
        self.view.setIconSize(QtCore.QSize(64, 64))
        bigFont = self.font()
        bigFont.setPointSize(24)
        self.view.setFont(bigFont)

        self.addButton = QtWidgets.QPushButton('Add image(s)')

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.view)
        layout.addWidget(self.addButton)

        self.addButton.clicked.connect(self.addImage)

    def addImage(self):
        paths, _ = QtWidgets.QFileDialog.getOpenFileNames(self, 
            'Select image(s)', '', 'Images (*.png *.jpg *.jpeg)')

        size = self.view.iconSize()
        for path in paths:
            source = QtGui.QPixmap(path)
            if source.isNull():
                continue

            if source.width() > size.width() or source.height() > size.height():
                source = source.scaled(size, QtCore.Qt.KeepAspectRatio, 
                    QtCore.Qt.SmoothTransformation)

            # create an empty squared image to keep vertical alignment
            square = QtGui.QPixmap(size)
            square.fill(QtCore.Qt.transparent)
            qp = QtGui.QPainter(square)
            rect = source.rect()
            rect.moveCenter(square.rect().center())
            qp.drawPixmap(rect, source)
            qp.end()

            name = QtCore.QFileInfo(path).baseName()
            item = QtWidgets.QListWidgetItem(name)
            item.setIcon(QtGui.QIcon(square))
            item.setToolTip(path)
            item.setSizeHint(size)
            self.view.addItem(item)

For more advanced customization, you can still use a QListWidget, but you also need to set a custom item delegate and override its paint() method.

musicamante
  • 41,230
  • 6
  • 33
  • 58
  • Thank you for the answer. This looks like exactly what I want, except the scroll for the `QListView` does not seem to be smooth. Meaning, instead of being able to scroll exactly how ever much I want, it snaps to certain points, which I assume is because it's making sure the element at the top of the window is displayed in full. Anyway to change this behaviour? – オパラ Apr 28 '22 at 06:52
  • Also, I would like to implement a way for users to select items (images) from the list, preferably with a checkbox. Any idea how I would go about that with a `QListView`? – オパラ Apr 28 '22 at 07:18
  • @オパラ read the documentation of QListWidget and all inherited or related classes. – musicamante Apr 28 '22 at 12:13