使用 QScrollArea 的视口修复背景渐变

Fix background-gradient with viewport for QScrollArea

请看下面的代码:

from PyQt5.QtWidgets import *
from PyQt5.QtGui import *


class MyScrollArea(QScrollArea):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        color_gradient = QLinearGradient(1, 0, 1, 1)
        color_gradient.setSpread(QGradient.PadSpread)
        color_gradient.setCoordinateMode(QGradient.ObjectMode)
        color_gradient.setColorAt(0, QColor('#8000D3'))
        color_gradient.setColorAt(0.5, QColor('#CB5CFF'))
        color_gradient.setColorAt(1, QColor('#8000D3'))

        palette = self.palette()
        palette.setBrush(QPalette.Window, QBrush(color_gradient))
        self.setPalette(palette)

        # Set widget and layout
        self.scroll_widget = QWidget()

        self.layout = QVBoxLayout()
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(30)
        self.scroll_widget.setLayout(self.layout)

        self.setWidget(self.scroll_widget)
        self.setWidgetResizable(True)

        # Add Labels
        for _ in range(40):
            label = QLabel("test")
            self.layout.addWidget(label)


if __name__ == '__main__':
    app = QApplication([])
    dialog = MyScrollArea()
    dialog.show()

    app.exec()

目前渐变设置为background-brush随滚动条滚动。

我知道我可以将背景图像设置为 fixed with the viewport,但我该如何修正渐变?
我能想到的唯一办法就是将渐变放在一个单独的标签中,然后用QScrollArea覆盖它,但我希望有一个更简单的解决方案。


更准确地说:
我希望无论滚动条的位置如何,QScrollArea 的背景总是看起来像这样(旋转以保存 space):

也就是说,整个渐变应该始终可见,而不仅仅是它的一部分。

这不能使用调色板来完成,因为渐变背景绘制在“滚动”小部件上,而您想要使其适应视口的可见部分。另外,请注意,在小部件上设置调色板会自动将其设置为其子项(由于 属性 继承),因此除非您确定其结果,否则通常应避免这样做。

解决方案是覆盖滚动区域的 paintEvent 并将该渐变用于视口。为确保此功能正常工作,为滚动区域设置的小部件必须设置透明背景。

class MyScrollArea(QScrollArea):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # note that I've made the gradient an attribute of the instance, so it can
        # be used in the paint event without recreating it everytime;
        <b>self.</b>color_gradient = QLinearGradient(1, 0, 1, 1)
        self.color_gradient.setSpread(QGradient.PadSpread)
        self.color_gradient.setCoordinateMode(QGradient.StretchToDeviceMode)
        self.color_gradient.setColorAt(0, QColor('#8000D3'))
        self.color_gradient.setColorAt(0.5, QColor('#CB5CFF'))
        self.color_gradient.setColorAt(1, QColor('#8000D3'))

        # ...
        # set the transparent background **only** for the container widget; note
        # the period before QWidget;
        self.scroll_widget.setStyleSheet('<b>.QWidget</b> {background: transparent;}')

    def paintEvent(self, event):
        qp = QPainter(self.viewport())
        qp.fillRect(self.viewport().rect(), self.color_gradient)