How do I apply BlurEffect(QtWidgets.QGraphicsBlurEffect) twice?

后端 未结 1 1150
眼角桃花
眼角桃花 2021-01-28 18:45

I have this code

import sys                                                         # +++
from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtWidgets import QApplicat         


        
相关标签:
1条回答
  • 2021-01-28 19:30

    You can't. Only one effect can be applied at once on a widget (and after that, no effect can be applied on any of its children or parents), at least for QWidgets.

    From QWidget.setGraphicsEffect():

    If there already is an effect installed on this widget, QWidget will delete the existing effect before installing the new effect.

    What happens is that as soon as you apply self.effect2 on subWidget, self.effect is removed from it and actually deleted. In PyQt terms, it means that the python object still exists, but not its C++ counterpart.

    UPDATE

    It seems that you still don't understand how a QGraphicsEffect works. The effect is NOT applied on the widgets you see with the blurred background. It is applied on the underlying widget (subWidget, in this case), and only on the rectangle(s) specified using the geometries of the widgets. You could even set the effectRect to any rect you want, even without any other widgets other than subWidget.

    If you need to apply the effect to more than one rectangle, then you should use setClipRegion and use a composite QRegion with it.
    Assuming you will always use QWidgets as a reference for the effect, and that the effect will always be applied to a widget that occupies the whole area of the window, you can use a "watch list" of widgets that need to be tracked, and update the effect whenever their geometry change.

    class BlurEffect(QtWidgets.QGraphicsBlurEffect):
        shouldEnable = True
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.watched = []
    
        def watchWidget(self, widget):
            widget.installEventFilter(self)
            self.watched.append(widget)
    
        def unwatchWidget(self, widget):
            if widget in self.watched:
                self.watched.remove(widget)
                self.update()
    
        def setEnabled(self, enabled):
            # in case you want to manually disable the effect, keep track of
            # the selected behavior
            self.shouldEnable = enabled
            super().setEnabled(enabled)
    
        def draw(self, qp):
            rects = []
            for widget in self.watched:
                if widget.isVisible():
                    rect = widget.rect()
                    if rect.isNull():
                        continue
                    # map the widget geometry to the window
                    rect.translate(
                        widget.mapTo(widget.window(), QtCore.QPoint()))
                    rects.append(rect)
                if not self.isEnabled() and self.shouldEnable:
                    super().setEnabled(True)
            if not rects:
                # no valid rect to be used, disable the effect if we should
                if not self.shouldEnable:
                    super().setEnabled(False)
                # otherwise, keep drawing the source with the effect applied
                # to the whole area of the widget
                else:
                    self.drawSource(qp)
            else:
                qp.save()
                # create a region that includes all rects
                rectRegion = QtGui.QRegion()
                for rect in rects:
                   rectRegion |= QtGui.QRegion(rect) 
                # clip the effect painting to the region
                qp.setClipRegion(rectRegion)
                # call the default implementation, which will draw the effect
                super().draw(qp)
                # get the full region that should be painted
                fullRegion = QtGui.QRegion(qp.viewport())
                # and subtract the effect rectangle used before
                fullRegion -= rectRegion
                qp.setClipRegion(fullRegion)
                # draw the *source*, which has no effect applied
                self.drawSource(qp)
                qp.restore()
    
        def eventFilter(self, source, event):
            # update the effect whenever a widget changes its geometry or
            # becomes visible
            if event.type() in (QtCore.QEvent.Resize, QtCore.QEvent.Move, 
                QtCore.QEvent.Show) and source.isVisible():
                    super().setEnabled(True)
                    self.update()
            # if a widget is going to be deleted, remove it from the list
            # of watched list; this is **VERY** important
            elif event.type() == QtCore.QEvent.DeferredDelete:
                self.unwatchWidget(source)
            return super().eventFilter(source, event)
    

    Important notes:

    • you have to use watchWidget for any widget for which you want to see the effect, including the topMenu; again, this doesn't mean that the effect is applied to those widget, but that their geometry is used for that;
    • obviously, there's no setEffectRect anymore;
    • with this implementation the effect disables itself automatically if all the watched widgets are hidden or their geometry is null, which means that you don't need to call self.effect.setEnabled() anymore;
    • even in this case (no watched widgets visible), you can still enable the effect to the whole area by explicitly calling setEnabled(True);

    Finally, I strongly suggest you to carefully study this code (and the previous) and the documentation about both QGraphicsEffect and QPainter (including the clipping section and all the related pages), and create some simple tests and examples by yourself to better understand how they work, before attempting to do what you're trying to achieve.

    0 讨论(0)
提交回复
热议问题