PyQt5:QStackedWidget 背景在动画期间是 visible/white

PyQt5: QStackedWidget background is visible/white during animation

我正在使用 Python 3.8 和 PyQt5 编写一个 GUI,其中“Designer”与 Qt5-Package 一起安装,然后(使用 pyuic5)转换为 .py 文件。

实际上我有一个淡入淡出的动画,当堆叠小部件的当前索引发生变化时(单击按钮时 changes/toggles)。

我的问题... 动画期间的白色背景到堆叠小部件的其他页面。我想让它透明,例如。不可见,因此背景中的图像是可见的(它位于另一个文件 'resources')。

我已经尝试为堆叠小部件设置固定背景。这样,在动画期间看不到白色。但是,我的目标是将其设置为透明以查看背景。在某些页面上,我找到了将样式表设置为“背景:透明”或将属性设置为“WA_TranslucentBackground”的信息,但所有这些对我都不起作用。样式表 background-color: rgba(0,0,0,0) 都不起作用。

我希望有人能帮助我解决我的问题。如果缺少任何信息,请发表评论。 提前致谢。


更新:要测试的一个代码片段...

有一个非常小的代码片段可以工作(我不记得我从哪个网站获得它)。我将背景涂成绿色,所以问题很明显。 运行 下面的代码 Python 3.8 我得到了...

很明显我在动画过程中遇到了同样的白色背景问题。

import sys
from PyQt5.QtCore import QTimeLine
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class FaderWidget(QWidget):
    def __init__(self, old_widget, new_widget): 
        QWidget.__init__(self, new_widget)    
        self.old_pixmap = QPixmap(new_widget.size())
        old_widget.render(self.old_pixmap)
        self.pixmap_opacity = 1.0  
        self.timeline = QTimeLine()
        self.timeline.valueChanged.connect(self.animate)
        self.timeline.finished.connect(self.close)
        self.timeline.setDuration(333)
        self.timeline.start()  
        self.resize(new_widget.size())
        self.show()
    
    def paintEvent(self, event):
        painter = QPainter()
        painter.begin(self)
        painter.setOpacity(self.pixmap_opacity)
        painter.drawPixmap(0, 0, self.old_pixmap)
        painter.end()
    
    def animate(self, value):
        self.pixmap_opacity = 1.0 - value
        self.repaint()

class StackedWidget(QStackedWidget):
    def __init__(self, parent = None):
        QStackedWidget.__init__(self, parent)
    
    def setCurrentIndex(self, index):
        self.fader_widget = FaderWidget(self.currentWidget(), self.widget(index))
        QStackedWidget.setCurrentIndex(self, index)
    
    def setPage1(self):
        self.setCurrentIndex(0)
    
    def setPage2(self):
        self.setCurrentIndex(1)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = QWidget()
    stack = StackedWidget()
    window.setStyleSheet("background-color: #0f0;")  # To see problem
    stack.addWidget(QCalendarWidget())
    editor = QTextEdit()
    editor.setPlainText("Hello world! "*100)
    stack.addWidget(editor)
    
    page1Button = QPushButton("Page 1")
    page2Button = QPushButton("Page 2")
    page1Button.clicked.connect(stack.setPage1)
    page2Button.clicked.connect(stack.setPage2)
    
    layout = QGridLayout(window)
    layout.addWidget(stack, 0, 0, 1, 2)
    layout.addWidget(page1Button, 1, 0)
    layout.addWidget(page2Button, 1, 1)
    
    window.show()
    
    sys.exit(app.exec_())

希望现在可以更轻松地提供帮助。


我试图简化我的 python 代码,但它仍然是一些代码...

main.py:

from sys import exit as sys_exit
from sys import argv as sys_argv
from outputFile import Ui_MainWindow
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QApplication, QStackedWidget, QWidget

import types
from funcs import FaderWidget, setCurrentIndex


class MyMainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(MyMainWindow, self).__init__()
        self.setupUi(self)
        self.pushButton.clicked.connect(self.btnFunc)

        ''' Overwrite method 'setCurrentIndex' from StackedWidget to animate it '''
        self.stackedWidget.setCurrentIndex = types.MethodType(setCurrentIndex, self.stackedWidget)

    def btnFunc(self):
        index = self.stackedWidget.currentIndex()
        if index == 1:
            self.stackedWidget.setCurrentIndex(0)
        else:
            self.stackedWidget.setCurrentIndex(1)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys_argv)
    MainWindow = MyMainWindow()
    MainWindow.show()
    sys_exit(app.exec_())

funcs.py:

from PyQt5.QtWidgets import QStackedWidget, QWidget
from PyQt5.QtCore import QTimeLine
from PyQt5.QtGui import QPixmap, QPainter

class FaderWidget(QWidget):
    def __init__(self, old_widget, new_widget):
        QWidget.__init__(self, new_widget)

        self.old_pixmap = QPixmap(new_widget.size())
        old_widget.render(self.old_pixmap)
        self.pixmap_opacity = 1.0

        self.timeline = QTimeLine()
        self.timeline.valueChanged.connect(self.animate)
        self.timeline.finished.connect(self.close)
        self.timeline.setDuration(333)
        self.timeline.start()

        self.resize(new_widget.size())
        self.show()

    def paintEvent(self, event):
        painter = QPainter()
        painter.begin(self)
        painter.setOpacity(self.pixmap_opacity)
        painter.drawPixmap(0, 0, self.old_pixmap)
        painter.end()

    def animate(self, value):
        self.pixmap_opacity = 1.0 - value
        self.repaint()


def setCurrentIndex(self, index):
    self.fader_widget = FaderWidget(self.currentWidget(), self.widget(index))
    QStackedWidget.setCurrentIndex(self, index)

我的问题预览:

问题是渲染小部件不考虑父级的背景,而只考虑小部件自己的背景:

If you enable this option, the widget's background is rendered into the target even if autoFillBackground is not set.

这意味着 render 将使用小部件的调色板 backgroundRole 作为背景色,无论它是否“透明”。

禁用默认的 DrawWindowBackground 标志对您没有多大帮助,即使您 fill 具有 Qt.transparent 的像素图,因为它会重叠在现有的小部件上,使其成为“模糊”。

解决方案是渲染顶层 window,但仅使用小部件的几何图形:

class FaderWidget(QWidget):
    def __init__(self, old_widget, new_widget):
        QWidget.__init__(self, new_widget)

        self.old_pixmap = QPixmap(old_widget.size())
        window = old_widget.window()
        geo = old_widget.rect().translated(old_widget.mapTo(window, QtCore.QPoint()))
        window.render(self.old_pixmap, sourceRegion=QtGui.QRegion(geo))
        # ...

请注意,您应该为 QPixmap 调整大小使用 old 小部件大小,这是因为如果您调整 window “新”小部件仍将具有以前的大小(堆叠的小部件大小或它在可见之前的大小):仅当 QStackedWidget 子级的几何形状可见或将要显示时,它们才会在调整大小时更新.