PySideのTableViewでDelegateを使う(3) もっとpaintする
今回は、 に続いて、 PySide の TableView で、Delegate 内の paint を駆使していい感じの GUI を作っていきます。
完成図
実行すると、こんな感じで表示されます。
表示の工夫としては、ウィンドウの大きさを変更すると、TableView のセルの数を変更して
サイズに収まる量だけ表示するようにします。
もう1つは、開いたタイミングだとすべてのセルを表示せず、スクロールバーが一番下に 来た時に
指定の行数以上のデータがある場合は追加でロードするようにします。
セルの中身は、デリゲートの paint で書きます。
https://gist.github.com/fereria/14e7af9e7e7c062a13bd2e3615ca3edc 全コードはこちら。
Item/Model/View を作る
まずは Model 部分とセルに表示するデータの構造を作ります。
今回の TableView は横の数が可変なので、Item は List 型で持つようにします。 1 セルごとにデータは管理したいので、この部分は
class DataItem(object):
def __init__(self, name="", description="", img=None):
self.name = name
self.description = description
self.img = img if img else DEFAULT_IMAGE
self.status = "default"
def backgroundColor(self):
return QtGui.QColor(255, 255, 100)
def data(self, column):
return self.name
def setData(self, column, value):
self.name = value
@classmethod
def sizeHint(self):
return QtCore.QSize(250, 200)
Item クラスを作成し、セル内に表示したい情報はこのクラスで管理するようにします。 今回はセルの大きさは共通なので、sizeHint は固定の値を返しますが 場合によってはここを変更する...といったこともできるようにしておきます。
Model
次に Model 部分。 TableView ですが、データは List なので これを適切な Row/Column でアクセスできるように index を実装します。
def index(self, row, column, parent):
index = ((row * self.columnNum) + column)
if index < len(self.items):
item = self.items[index]
return self.createIndex(row, column, item)
return QtCore.QModelIndex()
(row * columnCount) + column が Index になるのですが、TableView の場合 何もないセルであっても index は実行されるので 範囲が井の場合は 空の ModelIndex を返します。
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return None
item = index.internalPointer()
if role == QtCore.Qt.UserRole:
return item
data は、DisplayRole は使用せず、UserRole で item を取得できるようにだけします 。 (表示は全部 Delegate のため)
def rowCount(self, parent=QtCore.QModelIndex()):
u"""行数を返す"""
count = int(len(self.items) / self.columnNum) + 1
if self.maxRowCount > count:
return count
else:
return self.maxRowCount
def addMaxRow(self, num=5):
if (len(self.items) / self.columnNum) > self.maxRowCount:
self.maxRowCount += num
self.layoutChanged.emit()
def changeColumnNum(self, num):
self.columnNum = num
self.layoutChanged.emit()
最後に、スクロールが一番したに来たら追加する仕組みをつくります。 構造はシンプルで、 rowCount で返す値に最大値を指定します。 そして、self.addMaxRow が呼ばれたときに追加分の Row をセットすると 上限が解放されるようになります。
あとは、横のサイズも変更できるように関数を追加します。
どちらの関数も layoutChanged.emit() すれば、表示を更新することができます。
View
最後にビュー部分。
def __init__(self,parent):
# (略)
self.verticalScrollBar().valueChanged.connect(self.moveSlider)
def resizeEvent(self, event):
count = int(event.size().width() / DataItem.sizeHint().width())
if self.columnCount != count:
self.columnCount = count
self.columnCountChange(count)
def moveSlider(self, value):
if value == self.verticalScrollBar().maximum():
self.model().addMaxRow()
def columnCountChange(self, num):
self.model().changeColumnNum(num)
まず、ビューサイズが変更された場合は、 resizeEvent で取得できます。 変更前が oldSize() 変更後が size() で取得できるので、横サイズで割って 配置できる数を取得します。
毎回 Emit すると遅くなりそうなので、値が変更したときのみ changeColumnNum を実行します。
スライドバーは、valueChanged で変更を取得できます。 この valueChanged で取得される最大値は、Model の RowCount です。 途中で rowCount が変わった場合は、value はそのままで max が変化します。
paint する
これで準備ができたので Delegate の paint でセル内を書いていきます。
def paint(self, painter, option, index):
"""
Cellの中の描画を行う
"""
data = index.data(QtCore.Qt.UserRole)
Delegate では、index のセルを描画を実装します。 データでそのセルの DataItem を取得して、以降は QPainter でひたすら書いていきます。
セルの範囲
描画するセルの範囲は、option.rect で取得します。 図にすると、上のような QRect で取得できるので、以降はこの矩形をベースにして 描画したい画像や文字を配置します。
margin = 10
imgHeight = (option.rect.height() / 2) - margin
if data.img:
imgRect = copy.deepcopy(option.rect)
img = QtGui.QImage(data.img)
imgWidth = (imgHeight / img.size().height()) * img.size().width()
imgRect.setWidth(imgWidth)
imgRect.setHeight(imgHeight)
pos = option.rect.center()
imgRect.moveCenter(pos)
imgRect.translate(0, ((imgHeight / 2) * -1) + margin)
painter.drawImage(imgRect, img)
まず、セルの矩形を複製します。 (複製しないと元の option.rect が書き換わるため)
画像はセルの縦幅の半分からマージンを引いた数を上限として、 縦横比がいい感じになるように調整。 そして、中央になるように移動します。
# # タイトル表示
titleRect = copy.deepcopy(option.rect)
titleRect.setY(imgRect.bottom() + margin)
titleRect.setHeight(30)
font = QtGui.QFont("メイリオ", 11)
font.setBold(True)
painter.setFont(font)
painter.drawText(titleRect, QtCore.Qt.AlignCenter | QtCore.Qt.AlignTop | QtCore.Qt.TextWordWrap, data.name)
descriptionRect = copy.deepcopy(option.rect)
descriptionRect.setY(titleRect.bottom() + margin)
descriptionRect.setX(titleRect.left() + margin)
descriptionRect.setWidth(descriptionRect.width() - (margin * 2))
descriptionRect.setHeight(50)
font = QtGui.QFont("メイリオ", 8)
font.setBold(False)
painter.setFont(font)
painter.drawText(descriptionRect, QtCore.Qt.AlignLeft | QtCore.Qt.TextWrapAnywhere,
data.description)
次に文字の配置。 基本は同じようにベースとなる矩形を複製し、文字エリアの矩形の縦幅を setHeight で変更。 drawText で、中央揃え+行が溢れたら改行できるようにしておき ます。
あとは必用に応じて paint 内に記述を追加すれば OK です。
まとめ
とりあえずこれでカスタムモデル・ビュー・デリゲートを使用した TableView ができました。 あとは DataItem に必用な情報を足して、paint 部分を拡張したりすれば いろいろ作れそうです。