PySide로 Drop Indicator를 독자적으로 그리기(흑마술로 QProxyStyle을 실현한다)

11345 단어 QtPySide파이썬
PySide에서 위젯의 배경색을 바꾸면 Drop Indicator(드래그 앤 드롭 시 어디에 드롭되는지 표시)가 보기 어려울 수 있다.
독자적으로 그려 Indicator를 눈에 띄게 하고 싶다.

그러나 일은 간단하지 않으며 StyleSheet에는 Drop Indicator 설정이 없습니다.

Qt로 Drop Indicator를 독자적으로 그리려면 QProxyStyle을 사용하지만 PySide 및 PyQt에서는 QProxyStyle이 존재하지 않는 것이다.

그래서 PySide를 Hack하여 Drop Indicator의 묘화를 횡단한다.
덧붙여 이 방법은 정당한 방법이 아닌 흑마술이다, 미래의 PySide에서도 유효한지는 보증할 수 없다.

우선은 정당한 방법으로 그리기를 가로 채어 보자.

합법적인 방법: 자신만의 스타일 만들기



우선은 정당한 방법을 생각하자.

스스로 QStyle 클래스를 상속해, QStyle::drawPrimitive 메소드를 오버라이드(override) 하는 방법이다.
내 환경은 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 클래스를 만듭니다.
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을 구현하고 있다.

이 클래스에서 스타일 인스턴스를 동적으로 프록시하여 QApplication에 스타일로 정의합니다.
proxy_style = ProxyStyle(vista_style)

QApplication.setStyle(proxy_style)



잘 된 것 같다.
아래와 같이 StyleSheet를 병용해 배경을 회색으로 해도 문제 없게 동작하고 있다.
tree_view.setStyleSheet("QTreeView { background-color: gray }")



요약



이 방법은 전혀 우아하지 않습니다.

View의 paintEvent를 오버라이드(override) 해, 스스로 모두 draw 하는 방법도 있지만, View의 종류마다 draw를 바꿀 필요가 있어 매우 귀찮다.
가능하면 스타일 설정으로 극복하고 싶은 곳이다.

더 좋은 방법이 있으면, 꼭 교수 바랍니다.

좋은 웹페이지 즐겨찾기