PySide6 GUI 编程(43):自定义 QWidget

2024-09-16 08:36:48 浏览数 (1)

重载 paintEvent 方法

paintEvent的作用

paintEvent 方法是一个重要的事件处理函数,用于自定义控件的绘制。

它在控件需要重新绘制时被调用,例如在窗口被遮挡后重新显示、控件大小改变、或调用 update() 方法时。

  • 自定义绘制: paintEvent 允许开发者在控件上绘制自定义内容,比如图形、文本、图像等。通过重写这个方法,可以实现复杂的自定义界面。
  • 处理绘制事件: 当控件需要更新其显示内容时,Qt 会自动调用 paintEvent。这包括窗口的重绘、控件的状态变化等。
  • 使用 QPainter: 在 paintEvent 中,通常会使用 QPainter 类来执行绘制操作。QPainter 提供了丰富的绘图功能,包括绘制线条、矩形、圆形、文本等。

paintEvent 的函数原型

代码语言:python代码运行次数:0复制
def paintEvent(self, event: QPaintEvent):
    # 自定义绘制代码

paintEvent示例代码

代码语言:python代码运行次数:0复制
from __future__ import annotations

from PySide6.QtCore import QRect, Qt
from PySide6.QtGui import QColor, QFont, QPainter
from PySide6.QtWidgets import QApplication, QWidget


class CustomWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Paint Event Example")
        self.resize(400, 400)

    def paintEvent(self, event):
        painter = QPainter(self)

        # 设置绘制颜色
        painter.setBrush(QColor(100, 150, 200))
        painter.setPen(Qt.GlobalColor.black)

        # 绘制一个矩形
        rect = QRect(50, 100, 300, 200)
        painter.drawRect(rect)

        # 绘制文本
        font = QFont('ComicShannsMono Nerd Font', 20)
        painter.setFont(font)
        painter.drawText(rect, Qt.AlignmentFlag.AlignCenter,
                         'Hello PySide6 paintEvent')


if __name__ == '__main__':
    app = QApplication([])
    # 创建并显示自定义窗口
    custom_widget = CustomWidget()
    custom_widget.show()
    app.exec()

运行效果

paintEvent 运行效果paintEvent 运行效果

自定义QWidget 实现 PowerBar

示例代码

代码语言:python代码运行次数:0复制
from __future__ import annotations

from PySide6.QtCore import QRect, QSize, Qt
from PySide6.QtGui import QBrush, QPainter, QPaintEvent
from PySide6.QtWidgets import QApplication, QDial, QSizePolicy, QVBoxLayout, QWidget


class PowerBar(QWidget):
    def __init__(self, parent: QWidget | None = None):
        super().__init__(parent)
        # 显式的设置控件的最小大小,这个值是静态值
        self.setMinimumSize(100, 100)

        # setSizePolicy 接受两个参数,分别表示控件在水平方向和垂直方向上的大小策略
        # QSizePolicy.Policy.MinimumExpanding 表示控件的最小扩展策略
        # 具体来说,这意味着控件可以在其最小大小的基础上扩展,但不会小于其最小大小
        # 换句话说,控件会尽量占据可用空间,但不会小于其定义的最小尺寸
        # Fixed                    # 0x0 控件的大小是固定的,不会随布局的变化而改变
        # Minimum                  # 0x1 控件的大小可以缩小到其最小大小,但不会小于这个最小值
        # MinimumExpanding         # 0x3 控件可以扩展到其最小大小以上,但不会小于其最小大小
        # Maximum                  # 0x4 控件的大小可以扩展到其最大大小,但不会超过这个最大值
        # Preferred                # 0x5 控件的大小是其首选大小,布局会尽量使控件达到这个大小,但可以根据可用空间进行调整
        # Expanding                # 0x7 控件可以扩展以填充可用空间,但没有最小或最大限制
        # Ignored                  # 0xd 控件的大小策略被忽略,布局管理器不会考虑这个控件的大小
        self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
        # self.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding)

    def sizeHint(self) -> QSize:
        # 当布局管理器计算控件的大小时,它会首先检查控件的最小大小
        # 如果最小大小大于 sizeHint 返回的建议大小,布局管理器将使用最小大小,而不是建议大小
        # return QSize(50, 50)
        return QSize(100, 100)

    # 重载 paintEvent 方法
    def paintEvent(self, event: QPaintEvent):
        # 通过合理地使用 update() 和 repaint() 方法,可以有效地控制控件的重绘,确保用户界面始终保持最新状态
        painter = QPainter(self)
        brush = QBrush()
        brush.setStyle(Qt.BrushStyle.Dense3Pattern)
        brush.setColor(Qt.GlobalColor.darkGreen)
        rect = QRect(0, 0, painter.device().width(), painter.device().height())
        painter.fillRect(rect, brush)


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

        self.v_layout = QVBoxLayout()
        self.bar = PowerBar()
        self.dial = QDial()
        self.dial.setRange(0, 100)
        self.dial.setValue(0)
        self.v_layout.addWidget(self.bar)
        self.v_layout.addWidget(self.dial)
        self.setLayout(self.v_layout)


if __name__ == "__main__":
    app = QApplication()
    ins = CustomWidget()
    ins.show()
    app.exec()

运行效果

拖动窗口触发 paintEvent 事件拖动窗口触发 paintEvent 事件

更完整的PowerBar

示例代码

代码语言:python代码运行次数:0复制
from __future__ import annotations

from PySide6.QtCore import QRect, QSize, Qt
from PySide6.QtGui import QBrush, QFont, QPainter, QPaintEvent
from PySide6.QtWidgets import QApplication, QDial, QLabel, QSizePolicy, QVBoxLayout, QWidget


class PowerBar(QWidget):
    def __init__(self, parent: QWidget, min_value: int = 0, max_value: int = 100):
        super().__init__(parent)
        self.min_dial_value = min_value
        self.max_dial_value = max_value
        self.current_value = self.min_dial_value

        # 显式的设置控件的最小大小,这个值是静态值
        self.setMinimumSize(100, 100)

        # setSizePolicy 接受两个参数,分别表示控件在水平方向和垂直方向上的大小策略
        # QSizePolicy.Policy.MinimumExpanding 表示控件的最小扩展策略
        # 具体来说,这意味着控件可以在其最小大小的基础上扩展,但不会小于其最小大小
        # 换句话说,控件会尽量占据可用空间,但不会小于其定义的最小尺寸
        # Fixed                    # 0x0 控件的大小是固定的,不会随布局的变化而改变
        # Minimum                  # 0x1 控件的大小可以缩小到其最小大小,但不会小于这个最小值
        # MinimumExpanding         # 0x3 控件可以扩展到其最小大小以上,但不会小于其最小大小
        # Maximum                  # 0x4 控件的大小可以扩展到其最大大小,但不会超过这个最大值
        # Preferred                # 0x5 控件的大小是其首选大小,布局会尽量使控件达到这个大小,但可以根据可用空间进行调整
        # Expanding                # 0x7 控件可以扩展以填充可用空间,但没有最小或最大限制
        # Ignored                  # 0xd 控件的大小策略被忽略,布局管理器不会考虑这个控件的大小
        self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
        # self.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding)

    def sizeHint(self) -> QSize:
        # 当布局管理器计算控件的大小时,它会首先检查控件的最小大小
        # 如果最小大小大于 sizeHint 返回的建议大小,布局管理器将使用最小大小,而不是建议大小
        # return QSize(50, 50)
        return QSize(300, 500)

    # 重载 paintEvent 方法
    def paintEvent(self, event: QPaintEvent):
        # 通过合理地使用 update() 和 repaint() 方法,可以有效地控制控件的重绘,确保用户界面始终保持最新状态
        painter = QPainter(self)
        brush = QBrush()
        brush.setStyle(Qt.BrushStyle.SolidPattern)
        brush.setColor(Qt.GlobalColor.gray)
        rect = QRect(0, 0, painter.device().width(), painter.device().height())
        painter.fillRect(rect, brush)

        # 绘制对应比例面积的矩形
        percent = self.current_value / self.max_dial_value
        real_height = int(percent * painter.device().height())
        y_pos = painter.device().height() - real_height
        dial_percent_rect = QRect(0,
                                  y_pos,
                                  painter.device().width(),
                                  real_height)
        dial_percent_brush = QBrush()
        dial_percent_brush.setStyle(Qt.BrushStyle.SolidPattern)
        if percent <= 0:
            dial_percent_brush.setColor(Qt.GlobalColor.darkGray)
        elif percent <= 0.3:
            dial_percent_brush.setColor(Qt.GlobalColor.darkGreen)
        elif 0.3 < percent <= 0.6:
            dial_percent_brush.setColor(Qt.GlobalColor.darkYellow)
        elif 0.6 < percent <= 0.8:
            dial_percent_brush.setColor(Qt.GlobalColor.darkBlue)
        else:
            dial_percent_brush.setColor(Qt.GlobalColor.darkRed)
        painter.fillRect(dial_percent_rect, dial_percent_brush)

        painter.end()

    def re_draw(self, current_value: int):
        self.current_value = current_value  # 更新当前值
        self.update()


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

        self.v_layout = QVBoxLayout()
        self.min_value = 0
        self.max_value = 100
        self.bar = PowerBar(self, self.min_value, self.max_value)
        self.label = QLabel('初始状态')
        self.label.setFont(QFont('ComicShannsMono Nerd Font Propo', 22))
        self.label.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter)
        self.label.setStyleSheet("background-color: gray;")
        self.dial = QDial()
        self.dial.setRange(self.min_value, self.max_value)
        self.dial.setValue(self.min_value)
        self.dial.valueChanged.connect(self.bar.re_draw)
        self.dial.valueChanged.connect(self.update_label)
        self.v_layout.addWidget(self.bar)
        self.v_layout.addWidget(self.label)
        self.v_layout.addWidget(self.dial)
        self.setLayout(self.v_layout)

    def update_label(self):
        if self.dial.value() <= 0:
            self.label.setStyleSheet("background-color: gray;")
        elif self.dial.value() <= 30:
            self.label.setStyleSheet("background-color: darkgreen;")
        elif 30 < self.dial.value() <= 60:
            self.label.setStyleSheet("background-color: #CCCC00;")
        elif 60 < self.dial.value() <= 80:
            self.label.setStyleSheet("background-color: darkblue;")
        else:
            self.label.setStyleSheet("background-color: darkred;")
        self.label.setText(f'Power Percent: {self.dial.value()}%')


if __name__ == "__main__":
    app = QApplication()
    ins = CustomWidget()
    ins.show()
    app.exec()

上述代码实现的效果为,当旋转 dial 时,灰色矩形中会由下向上升起带有颜色的矩形,矩形高度随着 dial 值的变化而变化:

代码语言:python代码运行次数:0复制
        # 绘制对应比例面积的矩形
        percent = self.current_value / self.max_dial_value
        real_height = int(percent * painter.device().height())
        y_pos = painter.device().height() - real_height
        dial_percent_rect = QRect(0,
                                  y_pos,
                                  painter.device().width(),
                                  real_height)

这段代码对应以下的逻辑示意图:

powerbar矩形坐标计算示意图powerbar矩形坐标计算示意图

示例效果

PowerBar 运行效果PowerBar 运行效果

0 人点赞