Search code examples
pythonqt5pyside2qstyle

drawControl not receiving correct QStyleOption


I'm trying to style my application though a QProxyStyle.

I create a StyleCustom class which override drawControl to draw my tabs in red or green depending on the tab state, and add the text on top of it.

However, using option.text gives me the following error :

AttributeError: 'PySide2.QtWidgets.QStyleOption' object has no attribute 'text'

I notice here that option is a QStyleOption when it should be a QStyleOptionTab if I'm not wrong.

If I comment the drawText command, no crash, but the tabs are not colored as they should be :

resulting window

I feel that somehow, the drawControl method is not receiving the options correctly. I have noticed a similar behaviour when trying to draw other control types.

Here is a basic example using a QTabWidget :

from PySide2 import QtWidgets, QtGui, QtCore

class StyleCustom(QtWidgets.QProxyStyle):

    def drawControl(self, element: QtWidgets.QStyle.ControlElement, option: QtWidgets.QStyleOption, painter: QtGui.QPainter, widget:QtWidgets.QWidget=None):
        if element == QtWidgets.QStyle.ControlElement.CE_TabBarTabLabel:
            if option.state == QtWidgets.QStyle.State_Selected:
                painter.save()
                painter.fillRect(option.rect, QtGui.QBrush("#ff0000"))
                painter.restore()
            else:
                painter.save()
                painter.fillRect(option.rect, QtGui.QBrush("#00ff00"))
                painter.restore()
            painter.drawText(option.rect, QtCore.Qt.AlignCenter, option.text)
        else:
            return super().drawControl(element, option, painter, widget)

class MyMainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.tab = QtWidgets.QTabWidget()
        self.widg_1 = QtWidgets.QWidget()
        self.widg_2 = QtWidgets.QWidget()
        self.setCentralWidget(self.tab)
        self.tab.addTab(self.widg_1, "test1")
        self.tab.addTab(self.widg_2, "test2")

if __name__=='__main__':
    app = QtWidgets.QApplication()
    app.setStyle(StyleCustom())
    window = MyMainWindow()
    window.show()
    app.exec_()

Solution

  • Generally the casting is done by default in PySide (and also in PyQt) but in this case it seems that not so a possible solution is to do it using shiboken2:

    import shiboken2
    from PySide2 import QtWidgets, QtGui, QtCore
    
    
    class StyleCustom(QtWidgets.QProxyStyle):
        def drawControl(
            self,
            element: QtWidgets.QStyle.ControlElement,
            option: QtWidgets.QStyleOption,
            painter: QtGui.QPainter,
            widget: QtWidgets.QWidget = None,
        ) -> None:
            if element == QtWidgets.QStyle.ControlElement.CE_TabBarTabLabel:
                (cpp_pointer,) = shiboken2.getCppPointer(option)
                option_tab = shiboken2.wrapInstance(cpp_pointer, QtWidgets.QStyleOptionTab)
    
                painter.save()
                painter.fillRect(
                    option_tab.rect,
                    QtGui.QBrush(
                        "#ff0000"
                        if option_tab.state == QtWidgets.QStyle.State_Selected
                        else "#00ff00"
                    ),
                )
                painter.restore()
                painter.drawText(option_tab.rect, QtCore.Qt.AlignCenter, option_tab.text)
            else:
                super().drawControl(element, option, painter, widget)