2
2

Qtで画像を表示できるリストを作成する

Last updated at Posted at 2023-10-17

はじめに

Qtを使用したことがある人なら一度はQListWidgetを使ったことがあると思います。
ですが、その見た目を変更したいと思った方も多いのではないでしょうか?

この記事では細かい解説は省いて、できるだけ簡潔にリストをカスタマイズしてみます。

  1. QAbstractItemModel
  2. QListView
  3. QStyledItemDelegate

まずは上記の3つのClassを使用して、シンプルなリストを作成します。

環境

  • Python 3.10
  • PySide2
  • QtPy

シンプルなリストの作成

Model

class CustomItemModel(QtCore.QAbstractItemModel):
    def __init__(self, parent=None, data=[]):
        super(CustomItemModel, self).__init__(parent)
        self.__items = data

    def index(self, row, column, parent):
        return self.createIndex(row, column, self.__items[row])

    def parent(self, index):
        return QtCore.QModelIndex()

    def rowCount(self, parent):
        return len(self.__items)

    def columnCount(self, parent):
        return 1

    def data(self, index, role=QtCore.Qt.DisplayRole):
        item = index.internalPointer()
        if role == QtCore.Qt.DisplayRole:
            return item.get('name')
        else:
            return None

item = index.internalPointer() は
self.createIndex()の第三引数 (self.__items[row]) の値が取得できます


View,Delegate

class CustomListView(QtWidgets.QListView):
    def __init__(self, parent=None):
        super(CustomListView, self).__init__(parent)
class CustomItemDelegate(QtWidgets.QStyledItemDelegate):
    def __init__(self, parent=None):
        super(CustomItemDelegate, self).__init__(parent)

    def paint(self, painter, option, index):
        painter.save()
        if option.state & QtWidgets.QStyle.State_Selected:
            painter.fillRect(option.rect, option.palette.highlight())
            painter.setPen(option.palette.color(QtGui.QPalette.HighlightedText))

        elif option.state & QtWidgets.QStyle.State_MouseOver:
            painter.fillRect(option.rect, option.palette.midlight())

        name = index.data(QtCore.Qt.DisplayRole)
        painter.drawText(option.rect, QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeft, name)
        painter.restore()

painter.save(),painter.restore()について
setPen,setBrush等で変更した設定が残った状態になるため、save,restoreでpainterの初期化しています。

data = [{'name': 'hoge'}, {'name': 'fuga'}]

model = CustomItemModel(data=data)
delegate = CustomItemDelegate()
view = CustomListView()
view.setModel(model)
view.setItemDelegate(delegate)

layout = QtWidgets.QHBoxLayout()
layout.addWidget(view)
ui = QtWidgets.QWidget()
ui.setLayout(layout)
ui.show()

image.png

ここまでで最低限ListWidgetの見た目を再現できました。
上記を踏まえてデータの追加、見た目を変更していきます。


見た目のカスタマイズ

Model

表示する画像をモデルに追加します。

data = [{'name': 'hoge', 'img':'./img.png'}, {'name': 'fuga'}]
CustomItemModel
    def data(self, index, role=QtCore.Qt.DisplayRole):
        item = index.internalPointer()
        if role == QtCore.Qt.DisplayRole:
            return item.get('name')
        elif role == QtCore.Qt.UserRole:
            if img:= item.get('img'):
                if os.path.exists(img):
                    return QtGui.QPixmap(img)
            return img
        else:
            return None

View

CustomListView
    def __init__(self, parent=None):
        super(CustomListView, self).__init__(parent)
        self.setFlow(QtWidgets.QListView.Flow.LeftToRight)
        self.setViewMode(QtWidgets.QListView.IconMode)
        self.setResizeMode(QtWidgets.QListView.Adjust)

  • setFlow : リストの流れを横に変更
  • setViewMode : IconModeに変更することでアイテムのサイズに応じて並べる
  • setResizeMode : Adjustでviewのサイズが変更されたとき、レイアウトを自動調整

image.png


Delegate

1.サイズの指定

以下の関数を追加します。

CustomItemDelegate
    def sizeHint(self, option, index):
        return QtCore.QSize(200,150)

image.png

2.レイアウトの調整
CustomItemDelegate
    def setRectPadding(self, rect , padding=0):
        if not padding:
            return rect
        margin = QtCore.QMargins() + padding
        rect = rect.marginsRemoved(margin / 2)
        frame_rect = self.__rect.marginsRemoved(margin)
        rect = frame_rect.intersected(rect)
        return rect


    def splitVRect(self, rect, y):
        rect1 = rect.adjusted(0, 0, 0, y - rect.height())
        rect2 = rect.adjusted(0, y, 0, 0)
        return rect1, rect2


    def paint(self, painter, option, index):
        painter.save()
        self.__rect = option.rect

        if option.state & QtWidgets.QStyle.State_Selected:
            painter.fillRect(option.rect, option.palette.highlight())
            painter.setPen(option.palette.color(QtGui.QPalette.HighlightedText))

        elif option.state & QtWidgets.QStyle.State_MouseOver:
            painter.fillRect(option.rect, option.palette.midlight())

        name = index.data(QtCore.Qt.DisplayRole)
        img  = index.data(QtCore.Qt.UserRole)

        txt_rect, img_rect = self.splitVRect(option.rect, 24)
        txt_rect = self.setRectPadding(txt_rect, 4)
        img_rect = self.setRectPadding(img_rect, 8)

        painter.drawText(txt_rect, QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeft, name)

        if img:
            img = img.scaled(img_rect.size(), QtCore.Qt.KeepAspectRatio)
            src_rect = QtCore.QRect((img.width()-img_rect.width())/2,
                                    (img.height()-img_rect.height())/2,
                                    img_rect.width(),
                                    img_rect.height())
            painter.drawPixmap(img_rect, img, src_rect)
        else:
            painter.fillRect(img_rect, option.palette.mid())
            painter.setPen(option.palette.color(QtGui.QPalette.HighlightedText))
            painter.drawText(img_rect, QtCore.Qt.AlignVCenter | QtCore.Qt.AlignCenter, 'NoImage')

        painter.restore()
  • 2つの関数でテキスト,画像用の領域の指定
  • imgがある場合、サイズを調整して画像用の領域内で中心に配置

image.png

まとめ

今回はListViewで画像を表示できるようになりました。
ListWidget比較して、複雑なデータでも必要な情報に応じて見た目をカスタマイズできるので、よりユーザーにとってわかりやすいUIが作成できるかと思います。

次回はこのリストを便利に使用するために、データの操作などもう少し具体的な機能の紹介を予定しています。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2