0

I'm using Python 3.8 and PyQt5 to write a GUI with "Designer" installed with the Qt5-Package and then translated (with pyuic5) to a .py file.

Actually I have a fading animation, when the current index of the stacked widget changes (it changes/toggles when clicking the push button).

My Problem... A white background during the animation to the other page of the stacked widget. I want to have it transparent eg. not visible, so that the image in the background is visible (it is located in another file 'resources').

I already tried to set a fixed background to the stacked widget. With this, no white color is visible during animation. However, my goal is to set it transparent to see the background. On some pages I found the info to set the stylesheet to "background:transparent" or to set attribute to "WA_TranslucentBackground", but all of that didn't work for me. The stylesheet background-color: rgba(0,0,0,0) worked neither.

I hope somebody can help me with my problem. If any information is missing please comment. Thank in advance.


UPDATE: One code snippet to test...

There's a very small code snippet that works (I don't remeber from which website I have it). I colored the background green, so the problem is visible. Running the following code with Python 3.8 I got...

IncorrectFadingAnimationAtQStackedWidgetSimplified

It's clearly visible that there I have the same problem with the white background during animation.

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_())

Hope now it's easier to help.


I tried to simplify my python code but it's still a bit of code...

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)

Preview of my problem: IncorrectFadingAnimationAtQStackedWidget

David
  • 65
  • 10

1 Answers1

2

The problem is that rendering a widget doesn't take into account the background of the parent, but only the widget's own background:

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

This means that render will use the widget's palette backgroundRole as background color, no matter if it's "transparent".

Disabling the default DrawWindowBackground flag won't help you much, even if you fill the pixmap with Qt.transparent, since it will overlap on the existing widget, making it "blurred".

The solution is to render the top level window, but only using the widget's geometry:

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))
        # ...

Note that you should use the old widget size for both the QPixmap and the resize, and that's because if you resize the window the "new" widget will still have the previous size (the stacked widget size or the size it had until it was visible): geometries of QStackedWidget children are updated on resize only when they are visible or are going to be shown.

musicamante
  • 41,230
  • 6
  • 33
  • 58
  • I tried to update the minimal code snippet, posted at "update..." , exactly the "FaderWidget" class with your suggestions, but I still have the white background during the animation. Unfortunately I think I still don't got it. Can you give me further information how I can fix my problem? – David Jul 04 '20 at 14:26
  • I based my code on yours and it works. How are you setting the background of the main window? Are you using `WA_TranslucentBackground`? – musicamante Jul 04 '20 at 14:40
  • Did you take the minimal code? I tried it on both, but I doesn't work. There are no further background settings in my minimal code. – David Jul 04 '20 at 15:17
  • I edited the minimal code with your suggestion and added these import statements: from PyQt5 import QtCore, QtGui – David Jul 04 '20 at 15:19
  • I just tested it on 2 linux machines and it works properly, without any "white" background, correctly showing the fading effect. What OS and PyQt version are you using? – musicamante Jul 04 '20 at 15:35
  • I'm using Windows 10 (1909, 64-bit) and PyQt5 (5.15.0) – David Jul 04 '20 at 15:38
  • Just to be sure, you're using the code with *my* update, which renders on the top level window (using `window = old_widget.window(); window.render(...)`), right? If that's the case, can you try adding `flags=QWidget.DrawChildren` to the arguments of `window.render(...)`? – musicamante Jul 04 '20 at 16:15
  • Exactly, I'm using your update with window = old_ ... . Now I've added 'flags=QWidget.DrawChildren' after 'sourceRegion=...' but still have the white background. Do I have to modify anything else at my minimal code from above? Also tried this on my Raspberry 3 with Raspberry OS / Raspbian and PyQt5 (5.11.3) / Python 3.7.3 – David Jul 04 '20 at 16:24
  • Where do I have to use the `WA_TranslucentBackground`? – David Jul 04 '20 at 17:50
  • Finally, I've got it. The solution was your code combined with `old_widget.setAttribute(Qt.WA_TranslucentBackground)` (without `flags=QWiget...`). Thank you very much. – David Jul 04 '20 at 18:00