はじめに
Qtを使用したことがある人なら一度はQListWidgetを使ったことがあると思います。
ですが、その見た目を変更したいと思った方も多いのではないでしょうか?
この記事では細かい解説は省いて、できるだけ簡潔にリストをカスタマイズしてみます。
- QAbstractItemModel
- QListView
- 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()
ここまでで最低限ListWidgetの見た目を再現できました。
上記を踏まえてデータの追加、見た目を変更していきます。
見た目のカスタマイズ
Model
表示する画像をモデルに追加します。
data = [{'name': 'hoge', 'img':'./img.png'}, {'name': 'fuga'}]
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
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のサイズが変更されたとき、レイアウトを自動調整
Delegate
1.サイズの指定
以下の関数を追加します。
def sizeHint(self, option, index):
return QtCore.QSize(200,150)
2.レイアウトの調整
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がある場合、サイズを調整して画像用の領域内で中心に配置
まとめ
今回はListViewで画像を表示できるようになりました。
ListWidget比較して、複雑なデータでも必要な情報に応じて見た目をカスタマイズできるので、よりユーザーにとってわかりやすいUIが作成できるかと思います。
次回はこのリストを便利に使用するために、データの操作などもう少し具体的な機能の紹介を予定しています。