[PYTHON] Draw your own Drop Indicator with PySide (Realize QProxyStyle with black magic)

If you change the background color of the widget in PySide, the Drop Indicator (display of where it will be dropped when dragging and dropping) may be difficult to see. I want to draw my own to make the Indicator stand out.

However, things are not easy, and StyleSheet does not have a Drop Indicator setting.

QProxyStyle is used to draw Drop Indicator independently in Qt, but QProxyStyle does not exist in PySide and PyQt.

So, Hack PySide to steal the Drop Indicator drawing. In addition, this method is not a legitimate black magic, and we cannot guarantee that it will be effective in future PySide.

First, let's steal the drawing in a legitimate way.

Legitimate way: create your own style

First, let's think of a legitimate method.

This is a way to extend the QStyle class yourself and override the QStyle :: drawPrimitive method. Since my environment is Windows, I inherit 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)

In the above example, the Drop Indicator is red and the frame is thick. Set this style class with QApplication :: setStyle.

my_style = MyWindowsStyle(parent_widget)

QApplication.setStyle(my_style)
2016-12-28_23h35_29.png

It seems to have worked, but it looks old-fashioned overall.

In the Windows version of PySide (Qt), the standard style is QWindowsVistaStyle instead of QWindowStyle. However, QWindowsVistaStyle is a dynamically loaded Plugin style, so class inheritance is not possible. Also, since it inherits a specific style class, it will have the same look and feel on Mac and Linux. If you don't mind these disadvantages, we recommend this method.

Now let's use black magic to steal the QWindows Vista Style drawing.

Q Create an instance of WindowsVistaStyle

First, create a QWindowsVistaStyle instance. Use the QStyleFactory class to generate styles.

vista_style = QStyleFactory.create("windowsvista")

You have now created a QWindowsVistaStyle instance.

By the way, check the standard style class and key name (character string passed to QStyleFactory.create) of each environment as follows.

QApplication.style().metaObject().className()
>>> QWindowsVistaStyle

QApplication.style().objectName()
>>> windowsvista

Create a Proxy class for your instance

Create a Proxy class that contains the style instance.

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)

As you can see, the delegation is done in a forcible and muddy way. By the way, the PySide object cannot use the delegation trick by `` `getattr```. It implements QProxyStyle with brute force black magic.

Dynamically proxy a style instance with this class and define it as a style in QApplication.

proxy_style = ProxyStyle(vista_style)

QApplication.setStyle(proxy_style)
2016-12-29_02h01_19.png

It seems to have worked. Even if you use StyleSheet together and make the background gray as shown below, it works without problems.

tree_view.setStyleSheet("QTreeView { background-color: gray }")
2016-12-29_02h07_40.png

Summary

This method is not elegant at all.

There is also a method of overriding the paintEvent of the view and drawing everything by yourself, but it is very troublesome because it is necessary to change the drawing for each type of view. If possible, I would like to survive by setting the style.

If there is a better way, please teach me.

Recommended Posts

Draw your own Drop Indicator with PySide (Realize QProxyStyle with black magic)
Train UGATIT with your own dataset
Solve your own maze with DQN