PySideのTableViewでDelegateを使う(2) paintする
前回の のデータをすこし修正して
各セルごとの描画やらを色々作ってみました。
全コードはかなり長いので
https://snippets.cacher.io/snippet/9efc9751db929a15c9a2
こちらを参照。
今回は ui ファイルは使用していません。
実行結果はこんな感じ。
今回のサンプルのポイントは
- セルを見やすくするために背景色を変更
- チェックボックスを画像で作成
- ステータス部分を描画で見やすくしてみる
- View のセルサイズを自動フィットさせる
この 3 つです。
前回の Delegate で説明したとお り、Delegate の paint 関数をオーバーライドすることで
見た目の描画を自由に作成することができます。
今回はこの描画部分でテストしていたときにけっこうハマった所をメモっておきます。
背景色を変更する
# Painter COLOR ----- #
BACKGROUND_BASE_COLOR_A = QtGui.QColor(255, 255, 255)
BACKGROUND_BASE_COLOR_B = QtGui.QColor(240, 240, 240)
STATUS_FONT_COLOR = QtGui.QColor(255, 255, 255)
BACKGROUND_SELECTED = QtGui.QColor(204, 230, 255)
BACKGROUND_FOCUS = QtGui.QColor(240, 248, 255)
FONT_BASE_COLOR = QtGui.QColor(0, 0, 0)
まず、paint 内で使用する色は別途変数で定義をしておきます。
なんで直書きしないかというと、コード内でどこにどの色を使用しているのかわかりやすくするためです。
私はなんとなくですが python ファイルのあたまにまとめて記載するようにしています。
def paint(self, painter, option, index):
# 背景色を指定する
data = index.data()
bgColor = BACKGROUND_BASE_COLOR_A
if (index.row() % 2) != 0:
bgColor = BACKGROUND_BASE_COLOR_B
if option.state & QtWidgets.QStyle.State_Selected:
bgColor = BACKGROUND_SELECTED
if option.state & QtWidgets.QStyle.State_HasFocus:
bgColor = BACKGROUND_FOCUS
brush = QtGui.QBrush(bgColor)
painter.fillRect(option.rect, brush)
まず、背景色について。
paint 内で描画する場合は、引数で渡される「painter(QPainter)」を使用します。
この paint は、セルごとにセルの数分実行されます。
どのセルの処理をしているかは「index」で取得することが出来ます。
今回は奇数行と偶数行で色を変えて見やすくしたかったので
まずはそのいずれかの色を指定します。
さらに、「選択されてる」あるいは 「ドラッグ中」の場合は別の色を表示するように
QStyle を使用して判定をします。
引数の option は、セルごとの様々な設定情報を受け取るためのもので
option.state は、現在のセルのステータスを
option.rect は、現在のセルの矩形を
それぞれ取得することができます。
指定のセル色を決めたら、 fillRect で 指定色でセルを塗りつぶします。
この painter で何かをしたい場合は、
- 塗りつぶし色を setBrush で指定
- 線の色を setPen で指定
- 描画する
という順番で処理を書きます。
一度セットすると、次の処理でもセットされた色や塗りつぶし方法の指定が引き継がれるのに
注意が必要です。
チェックボックスを画像で描画する
次にチェックボックス。
一応デフォルトでもチェックボックスボタンは用意されているのですが
それだと位置調整とか色々めんどうだったのと
画像でやれば見た目もおしゃれにしやすいので画像でつくってみます。
まず、チェックの ON と OFF の画像を用意しておきます。
私のサンプルは
http://modernuiicons.com/
このサイトのアイコンを使用しました。
if index.column() == 1:
if data:
pix = QtGui.QPixmap(CHECK_IMG)
else:
pix = QtGui.QPixmap(UNCHECK_IMG)
rect = self.getCheckBoxRect(option)
painter.drawPixmap(rect, pix)
まず paint 内の描画。
def getCheckBoxRect(self, option, imgSize=30):
return QtCore.QRect(option.rect.left() + (option.rect.width() / 2) - (imgSize / 2),
option.rect.top() + (option.rect.height() / 2) - (imgSize / 2),
imgSize,
imgSize)
ボタンの表示位置は、QRect で設定するのですが、
描画部分以外にも「ボタンを押した」処理を位置で判定するのに使用したいので
別途関数にしておきます。
位置は、現在のセルの中央に imgSize の大きさで表示するようにします。
そして、画像を drawPixmap で表示します。
def editorEvent(self, event, model, option, index):
if index.column() == 1:
if self.getCheckBoxRect(option).contains(event.pos().x(), event.pos().y()):
if event.type() == QtCore.QEvent.MouseButtonPress:
currentValue = model.items[index.row()].data(index.column())
model.setData(index, not currentValue)
return True
return False
クリックしたときの挙動は基本前回と同じです。
画像表示位置と同じ矩形でマウス位置が範囲内かどうかを判定して、
範囲内でクリックされている場合は、セルの Bool 値を変更します。
この部分も若干処理から変更していて
前回は
model.items[index.row()].setData(index.column(), not currentValue)
model.layoutChanged.emit()
このように、model の items(表示しているオブジェクトの配列)の setData で値を指定していましたが
model の setData 関数をオーバーライドした関数を作成し、
そちら経由で値を登録するようにしました。
def setData(self, index, value, role=QtCore.Qt.EditRole):
if role == QtCore.Qt.EditRole:
index.data(QtCore.Qt.UserRole).setData(index.column(), value)
self.headerDataChanged.emit(QtCore.Qt.Vertical, index.row(), index.row())
self.dataChanged.emit(index, index)
今までは、値を変更したときに View をアップデートする良い方法が layoutChanged しか思いつかず
もっと良い方法はないか探していたのですが
setData でセットして、 dataChanged と headerDataChanged するほうが色々と都合が良い(あとで詳しく説明)
のが分かったので、この方式にしました。