Qt: 自定义 QWidget 响应 StyleSheet 样式

Qt: 自定义 QWidget 响应 StyleSheet 样式

1. 必须重写 paintEvent()

根据官方文档的说明,如果希望自定义的 QWidget 派生类能够响应 StyleSheet 中定义的样式,就必须用如下代码重写 paintEvent() 方法。(其实应该叫做实现 paintEvent(),因为 QWidget::paintEvent() 本来是个空函数,啥都没做)

换成 Python + PySide6 语法就是:

class CustomWidget(QWidget):
    def __init__(self, parent: QWidget = None):
        super().__init__(parent)
        ...
    
    def paintEvent(self, event):
        opt = QStyleOption()
        opt.initFrom(self)
        painter = QPainter(self)
        self.style().drawPrimitive(QStyle.PrimitiveElement.PE_Widget, opt, painter, self)
        pass

2. paintEvent() 的作用

首先需要知道,所有 QWidget 派生类需要绘制的时候,都是调用 paintEvent() 方法进行绘制!(但 QWidget::paintEvent() 是空函数,具体实现都由派生类自己来做,比如 QLabel::paintEvent())
① 如果继承自某个内建 widget(比如 QLabel, QPushButton 这些),那么重写该方法将会覆盖父类的绘制行为。
② 如果该 widget 还有 child widgets 的话,在执行完自己的 paintEvent() 之后,还会接着自动调用所有 child widgets 的 paintEvent()!
(其实并不是所有,只需要调用与该 event.rect() 有交集的 child widgets 的 paintEvent() 方法。)
③ 对于由内建 widget 组合而成的自定义 widget。比如说我新建了一个 CustomWidget,它其实是由 QLabel + QPushButton 组合起来的。
那么根据 ② 中所述,是可以不需要重写 paintEvent() 方法的。child widgets 会自行绘制自己。
④ 但是!如果不重写 paintEvent() 的话,自定义的 widget 就没办法响应 StyleSheet 中与其相匹配的样式。即在 qss 文件中,或者直接通过 setStyleSheet() 设置的 CustomWidget { background-color: #acdbb7 } 就无法生效。

3. 响应 StyleSheet 属性选择器的样式

CustomWidget[clicked="true"] { 
    background-color: #dc5f5f 
}

这是 StyleSheet 属性选择器的例子。

要想让自定义控件 CustomWidget 的实例响应这个 [clicked="true"] 属性选择器。除了 CustomWidget 必须重写 paintEvent() 外,还必须在代码中给 CutomWidget 实例设置 clicked “属性”,还必须调用 QStyle.polish() 方法来重新加载 StyleSheet。

widget.setProperty('clicked', True)

widget.style().polish(widget)

4. 示例代码

QSS = """
    CustomWidget { 
        background-color: #acdbb7 
    }

    CustomWidget[clicked="true"] { 
        background-color: #dc5f5f 
    }
"""

class CustomWidget(QWidget):
    def __init__(self, title: str, parent: QWidget = None):
        super().__init__(parent)

        # 1. 图标
        self.label_icon = QLabel(self)
        self.label_icon.setPixmap(self.style().standardIcon(QStyle.StandardPixmap.SP_TitleBarMenuButton).pixmap(32, 32))
        
        # 2. title
        self.label_title = QLabel(self)
        self.label_title.setText(title)

        self.horizontalLayout = QHBoxLayout(self)
        self.horizontalLayout.addWidget(self.label_icon)
        self.horizontalLayout.addWidget(self.label_title)
        pass
    
    def paintEvent(self, event):
        opt = QStyleOption()
        opt.initFrom(self)
        painter = QPainter(self)
        self.style().drawPrimitive(QStyle.PrimitiveElement.PE_Widget, opt, painter, self)
        pass

    def mousePressEvent(self, ev):
        self.setProperty('clicked', True)
        self.style().polish(self)
        pass
    
if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyleSheet(QSS)

    main_window = QMainWindow()
    main_window.setCentralWidget(CustomWidget('测试'))
    main_window.show()
    sys.exit(app.exec())
    pass

 

Leave a Reply

Your email address will not be published. Required fields are marked *

TOC