PyQt6中重写QSpinBox的paintEvent实现顶层绘制
QSpinBox子类化绘制问题解决方案
QSpinBox是个复合控件,内部包含子控件(比如用来显示数值的QLineEdit)。父控件的paintEvent执行完后,子控件会接着自己绘制,所以你在父类paintEvent之后画的矩形,会被QLineEdit这类子控件盖住,这就是你遇到的问题根源。
问题1:让绘制内容位于QSpinBox所有元素之上
方法1:添加顶层覆盖控件
在CustomSpinBox内部加一个透明的子控件,让它完全覆盖整个SpinBox区域,然后在这个覆盖控件的paintEvent里画内容。这个覆盖控件会处于所有子控件的最顶层,所以画的内容能盖住所有元素。
代码示例:
import sys from PyQt6.QtCore import Qt from PyQt6.QtWidgets import QApplication, QSpinBox, QWidget from PyQt6.QtGui import QPaintEvent, QBrush, QColor, QPainter class OverlayWidget(QWidget): def __init__(self, parent=None): super().__init__(parent) # 让覆盖控件不拦截鼠标操作,保证SpinBox的箭头还能点 self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents) self.setGeometry(parent.rect()) def paintEvent(self, e: QPaintEvent) -> None: painter = QPainter(self) painter.setBrush(QBrush(QColor('orange'), Qt.BrushStyle.SolidPattern)) painter.drawRect(0, 0, self.width(), self.height()) class CustomSpinBox(QSpinBox): def __init__(self, parent=None): super().__init__(parent) self.overlay = OverlayWidget(self) def resizeEvent(self, event): super().resizeEvent(event) # 控件尺寸变化时,让覆盖控件跟着调整大小 self.overlay.setGeometry(self.rect()) if __name__ == '__main__': app = QApplication(sys.argv) window = QWidget() window.setGeometry(500, 200, 400, 300) spin_box = CustomSpinBox(window) spin_box.move(100, 100) spin_box.setFixedSize(120, 40) window.show() app.exec()
方法2:用事件过滤器监听子控件绘制
给SpinBox的所有子控件装个事件过滤器,等子控件画完之后,再画你的覆盖内容。这种方法不用额外加控件,但逻辑稍复杂:
import sys from PyQt6.QtCore import Qt, QEvent from PyQt6.QtWidgets import QApplication, QSpinBox, QWidget from PyQt6.QtGui import QPaintEvent, QBrush, QColor, QPainter class CustomSpinBox(QSpinBox): def __init__(self, parent=None): super().__init__(parent) # 给所有子控件安装事件过滤器 for child in self.children(): child.installEventFilter(self) def eventFilter(self, obj, event): if event.type() == QEvent.Type.Paint: # 先让子控件完成绘制 result = super().eventFilter(obj, event) # 再画覆盖内容 painter = QPainter(self) painter.setBrush(QBrush(QColor('orange'), Qt.BrushStyle.SolidPattern)) painter.drawRect(0, 0, self.width(), self.height()) return result return super().eventFilter(obj, event) def paintEvent(self, e: QPaintEvent) -> None: super().paintEvent(e) # 这里也要画一次,覆盖SpinBox自身的箭头等区域 painter = QPainter(self) painter.setBrush(QBrush(QColor('orange'), Qt.BrushStyle.SolidPattern)) painter.drawRect(0, 0, self.width(), self.height()) if __name__ == '__main__': app = QApplication(sys.argv) window = QWidget() window.setGeometry(500, 200, 400, 300) spin_box = CustomSpinBox(window) spin_box.move(100, 100) spin_box.setFixedSize(120, 40) window.show() app.exec()
问题2:完全不绘制原内容
要彻底隐藏SpinBox的原始内容,需要两步:
- 不调用父类的
paintEvent,避免画控件的框架和箭头; - 隐藏内部的数值显示子控件(QLineEdit)。
代码示例:
import sys from PyQt6.QtCore import Qt from PyQt6.QtWidgets import QApplication, QSpinBox, QWidget from PyQt6.QtGui import QPaintEvent, QBrush, QColor, QPainter, QFont, QFontMetrics class CustomSpinBox(QSpinBox): def __init__(self, parent=None): super().__init__(parent) # 隐藏默认的数值输入框 self.lineEdit().hide() def paintEvent(self, e: QPaintEvent) -> None: # 不调用super().paintEvent(e),完全自定义绘制 painter = QPainter(self) # 画橙色背景 painter.setBrush(QBrush(QColor('orange'), Qt.BrushStyle.SolidPattern)) painter.drawRect(0, 0, self.width(), self.height()) # 可选:自己画当前数值(如果需要保留数值显示) painter.setPen(Qt.GlobalColor.black) font = QFont() font.setPointSize(12) painter.setFont(font) text = str(self.value()) metrics = QFontMetrics(font) # 让文本居中显示 x = (self.width() - metrics.horizontalAdvance(text)) // 2 y = (self.height() + metrics.ascent()) // 2 painter.drawText(x, y, text) if __name__ == '__main__': app = QApplication(sys.argv) window = QWidget() window.setGeometry(500, 200, 400, 300) spin_box = CustomSpinBox(window) spin_box.move(100, 100) spin_box.setFixedSize(120, 40) spin_box.setValue(50) # 设置初始值 window.show() app.exec()
内容的提问来源于stack exchange,提问作者Jimmy Lander
相关产品推荐
相关产品推荐

