PySideでウィジェットの背景色を変えていると、Drop Indicator(ドラッグ&ドロップ時にどこにドロップされるかの表示)が見にくい場合がある。
独自に描画してIndicatorを目立つようにしたい。
しかし、事は簡単ではなく、StyleSheetではDrop Indicatorの設定がない。
QtでDrop Indicatorを独自描画するにはQProxyStyleを使うのだが、PySideおよびPyQtではQProxyStyleが存在しないのである。
そこで、PySideをHackしてDrop Indicatorの描画を横取りする。
なお、この方法は正当な方法ではない黒魔術である、未来のPySideでも有効かは保証できない。
まずは正当な方法で描画を横取りしてみよう。
正当な方法:独自のスタイルを作成する
まずは正当な方法を考えよう。
自分でQStyleクラスを継承して、QStyle::drawPrimitiveメソッドをオーバーライドする方法である。
私の環境はWindowsなので、QWindowsStyleを継承する。
class MyWindowsStyle(QWindowsStyle):
def drawPrimitive(self, element, option, painter, widget):
if element == QStyle.PE_IndicatorItemViewItemDrop:
pen = QPen(Qt.red)
pen.setWidth(2)
painter.setPen(pen)
if not option.rect.isNull():
painter.drawRect(option.rect)
return
super(MyWindowsStyle, self).drawPrimitive(element, option, painter, widget)
上の例ではDrop Indicatorを赤にし枠を太くしている。
このスタイルクラスをQApplication::setStyleで設定する。
my_style = MyWindowsStyle(parent_widget)
QApplication.setStyle(my_style)
うまくいったようだが、見た目が全体的に古臭くなった。
Windows版のPySide(Qt)では、標準のスタイルはQWindowStyleではなくQWindowsVistaStyleが適用される。
しかし、QWindowsVistaStyleは動的に読み込まれるPluginスタイルであるため、クラス継承ができないのだ。
また特定のスタイルクラスを継承してしまうため、MacやLinuxでも同一のルック&フィールになってしまう。
これらのデメリットが気にならないのであれば、この方法をお勧めする。
それでは、黒魔術を使ってQWindowsVistaStyleの描画を横取りしてみよう。
QWindowsVistaStyleのインスタンスを生成
まずはQWindowsVistaStyleインスタンスの生成である。
スタイルの生成にはQStyleFactoryクラスを使う。
vista_style = QStyleFactory.create("windowsvista")
これでQWindowsVistaStyleインスタンスが生成された。
ちなみに各環境の標準のスタイルクラスとキー名(QStyleFactory.createに渡す文字列)を調べるのは以下のようにする。
QApplication.style().metaObject().className()
>>> QWindowsVistaStyle
QApplication.style().objectName()
>>> windowsvista
インスタンスのProxyクラスを作る
スタイルインスタンスを包括するProxyクラスを作成する。
class ProxyStyle(QCommonStyle):
TranslateMethods = [
'drawComplexControl', 'drawControl', 'drawItemText', 'generatedIconPixmap',
'hitTestComplexControl', 'pixelMetric', 'polish', 'sizeFromContents',
'sizeFromContents', 'standardPixmap', 'styleHint', 'styleHint',
'subControlRect', 'subElementRect', 'unpolish'
]
def __init__(self, style, parent=None):
super(ProxyStyle, self).__init__(parent)
self._style = style
for method_name in self.TranslateMethods:
setattr(self, method_name, getattr(self._style, method_name))
def drawPrimitive(self, element, option, painter, widget):
if element == QStyle.PE_IndicatorItemViewItemDrop:
pen = QPen(Qt.red)
pen.setWidth(2)
painter.setPen(pen)
if not option.rect.isNull():
painter.drawRect(option.rect)
return
self._style.drawPrimitive(element, option, painter, widget)
見てもらえれば分かるが、強引かつ泥臭い方法で委譲をしている。
ちなみにPySideオブジェクトは__getattr__
による委譲トリックは使えない。
強引な黒魔術でQProxyStyleを実装している。
このクラスでスタイルインスタンスを動的にProxyして、QApplicationにスタイルとして定義する。
proxy_style = ProxyStyle(vista_style)
QApplication.setStyle(proxy_style)
うまくいったようだ。
以下のようにStyleSheetを併用して背景を灰色にしても問題なく動作している。
tree_view.setStyleSheet("QTreeView { background-color: gray }")
まとめ
この方法は全くエレガントではない。
ViewのpaintEventをオーバーライドして、自前ですべて描画する方法もあるが、Viewの種類ごとに描画を変える必要があり、非常に面倒である。
できればスタイルの設定で乗り切りたいところである。
もっと良い方法があったら、是非ご教授願いたい。