简体   繁体   English

我如何应用 BlurEffect(QtWidgets.QGraphicsBlurEffect) 两次?

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

I have this code我有这个代码

import sys                                                         # +++
from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QVBoxLayout, QLabel
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5.QtCore import (QCoreApplication, QMetaObject, QObject, QPoint,
    QRect, QSize, QUrl, Qt)
from PyQt5 import QtCore, QtGui, QtWidgets


'''
from PySide2.QtCore import Qt, QUrl
from PySide2.QtWidgets import QApplication, QWidget, QMainWindow, QVBoxLayout, QLabel
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtCore import (QCoreApplication, QMetaObject, QObject, QPoint,
    QRect, QSize, QUrl, Qt)
from PySide2 import QtCore, QtGui, QtWidgets
'''



class BlurEffect(QtWidgets.QGraphicsBlurEffect):
    effectRect = None

    def setEffectRect(self, rect):
        self.effectRect = rect
        self.update()

    def draw(self, qp):
        if self.effectRect is None or self.effectRect.isNull():
            # no valid effect rect to be used, use the default implementation
            super().draw(qp)
            print('bao')
        else:
            qp.save()
            # clip the drawing so that it's restricted to the effectRect
            qp.setClipRect(self.effectRect)
            # 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
            fullRegion -= QtGui.QRegion(self.effectRect)
            qp.setClipRegion(fullRegion)
            # draw the *source*, which has no effect applied
            self.drawSource(qp)
            qp.restore()


class Window(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()

        background = QtGui.QPixmap('background.png')

        # apply a background to this widget, note that this only serves for the
        # graphics effect to know what's outside the boundaries
        p = self.palette()
        p.setBrush(p.Window, QtGui.QBrush(background))
        self.setPalette(p)

        self.resize(background.size())

        # this layout is only for the child "sub" widget
        mainLayout = QtWidgets.QVBoxLayout(self)
        mainLayout.setContentsMargins(0, 0, 0, 0)

        # the "sub" widget, that contains the main interface
        self.subWidget = QtWidgets.QWidget()
        mainLayout.addWidget(self.subWidget)
        # set the background for the subwidget; note that we can't use setPalette()
        # because palette and fonts are inherited by children; using ".QWidget"
        # we ensure that the background is only applied to the subwidget
        self.subWidget.setStyleSheet('''
            .QWidget {
                background-image: url(background.png);
            }
        ''')

        # some random widgets
        subLayout = QtWidgets.QGridLayout(self.subWidget)
        for row in range(3):
            for col in range(3):
                btn = QtWidgets.QPushButton()
                subLayout.addWidget(btn, row, col)

        btn.setText('Open menu')
        btn.setFocus()
        btn.clicked.connect(self.openMenu)

        # create an instance of our effect subclass, and apply it to the subwidget
        self.effect = BlurEffect()
        self.subWidget.setGraphicsEffect(self.effect)
        self.effect.setEnabled(False)
        self.effect.setBlurRadius(10)









        # create the menu container, that *HAS* to have this main widget as parent
        self.topMenu = QtWidgets.QWidget(self)
        self.topMenu.setVisible(False)
        self.topMenu.setFixedWidth(200)
        # move the menu outside the window left margin
        self.topMenu.move(-self.topMenu.width(), 0)

        menuLayout = QtWidgets.QVBoxLayout(self.topMenu)
        menuLayout.addSpacing(20)
        for b in range(4):
            btn = QtWidgets.QPushButton('Button {}'.format(b + 1))
            menuLayout.addWidget(btn)

        menuLayout.addSpacing(10)

        closeButton = QtWidgets.QPushButton('Close menu')
        menuLayout.addWidget(closeButton)
        closeButton.clicked.connect(self.closeMenu)
        # a stretch to ensure that the items are always aligned on top
        menuLayout.addStretch(1)

        # an animation that will move the menu laterally
        self.menuAnimation = QtCore.QVariantAnimation()
        self.menuAnimation.setDuration(500)
        self.menuAnimation.setEasingCurve(QtCore.QEasingCurve.OutQuart)
        self.menuAnimation.setStartValue(-self.topMenu.width())
        self.menuAnimation.setEndValue(0)
        self.menuAnimation.valueChanged.connect(self.resizeMenu)
        self.menuAnimation.finished.connect(self.animationFinished)

        # a simple transparent widget that is used to hide the menu when
        # clicking outside it; the event filter is to capture click events
        # it may receive
        self.clickGrabber = QtWidgets.QWidget(self)
        self.clickGrabber.installEventFilter(self)
        self.clickGrabber.setVisible(False)

    def resizeMenu(self, value):
        # move the menu and set its geometry to the effect
        self.topMenu.move(value, 0)
        self.effect.setEffectRect(self.topMenu.geometry())

    def openMenu(self):
        if self.topMenu.x() >= 0:
            # the menu is already visible
            return
        # ensure that the menu starts hidden (that is, with its right border
        # aligned to the left of the main widget)
        self.topMenu.move(-self.topMenu.width(), 0)
        self.topMenu.setVisible(True)
        self.topMenu.setFocus()

        # enable the effect, set the forward direction for the animation, and
        # start it; it's important to set the effect rectangle here too, otherwise
        # some flickering might show at the beginning
        self.effect.setEffectRect(self.topMenu.geometry())
        self.effect.setEnabled(True)
        self.menuAnimation.setDirection(QtCore.QVariantAnimation.Forward)
        self.menuAnimation.start()

        # "show" the grabber (it's invisible, but it's there) and resize it
        # to cover the whole window area
        self.clickGrabber.setGeometry(self.rect())
        self.clickGrabber.setVisible(True)
        # ensure that it is stacked under the menu and above everything else
        self.clickGrabber.stackUnder(self.topMenu)

    def closeMenu(self):
        # in case that the menu has changed its size, set again the "start" value
        # to its negative width, then set the animation direction to backwards
        # and start it
        self.menuAnimation.setStartValue(-self.topMenu.width())
        self.menuAnimation.setDirection(QtCore.QVariantAnimation.Backward)
        self.menuAnimation.start()
        # hide the click grabber
        self.clickGrabber.setVisible(False)

    def animationFinished(self):
        # if the animation has ended and the direction was backwards it means that
        # the menu has been closed, hide it and disable the effect
        if self.menuAnimation.direction() == QtCore.QVariantAnimation.Backward:
            self.topMenu.hide()
            self.effect.setEnabled(False)

    def focusNextPrevChild(self, next):
        if self.topMenu.isVisible():
            # a small hack to prevent tab giving focus to widgets when the
            # menu is visible
            return False
        return super().focusNextPrevChild(next)

    def eventFilter(self, source, event):
        if source == self.clickGrabber and event.type() == QtCore.QEvent.MouseButtonPress:
            # the grabber has been clicked, close the menu
            self.closeMenu()
        return super().eventFilter(source, event)

    def resizeEvent(self, event):
        super().resizeEvent(event)
        # always set the menu height to that of the window
        self.topMenu.setFixedHeight(self.height())
        # resize the grabber to the window rectangle, even if it's invisible
        self.clickGrabber.setGeometry(self.rect())
        if self.topMenu.isVisible():
            # resize the effect rectangle
            self.effect.setEffectRect(self.topMenu.geometry())



if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)

    w = Window()
    w.resize(640, 570)  
    w.show()
    sys.exit(app.exec_())

I need to be able to apply blur effect to multiple widget at one time我需要能够一次将模糊效果应用于多个小部件

And the problem is when I add问题是当我添加

    self.effect2 = BlurEffect()
    self.subWidget.setGraphicsEffect(self.effect2)
    self.effect2.setEnabled(False)
    self.effect2.setBlurRadius(10)

after

    self.effect = BlurEffect()
    self.subWidget.setGraphicsEffect(self.effect)
    self.effect.setEnabled(False)
    self.effect.setBlurRadius(10)

I get this error我收到这个错误

Traceback (most recent call last): File "C:\\Users\\user\\Desktop\\test\\widgets\\menu.py", line 157, in openMenu self.effect.setEffectRect(self.topMenu.geometry()) File "C:\\Users\\user\\Desktop\\test\\widgets\\menu.py", line 15, in setEffectRect self.update() RuntimeError: wrapped C/C++ object of type BlurEffect has been deleted回溯(最近一次调用):文件“C:\\Users\\user\\Desktop\\test\\widgets\\menu.py”,第 157 行,在 openMenu self.effect.setEffectRect(self.topMenu.geometry()) 文件“C :\\Users\\user\\Desktop\\test\\widgets\\menu.py",第 15 行,在 setEffectRect self.update() 中运行时错误:BlurEffect 类型的包装 C/C++ 对象已被删除

Does anyone know how to fix this?有谁知道如何解决这一问题?

photoshop照相馆在此处输入图片说明

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.一次只能在一个小部件上应用一个效果(之后,不能对它的任何子级或父级应用任何效果),至少对于 QWidgets。

From QWidget.setGraphicsEffect() :QWidget.setGraphicsEffect()

If there already is an effect installed on this widget, QWidget will delete the existing effect before installing the new effect.如果这个小部件上已经安装了一个效果,QWidget在安装新效果之前删除现有的效果。

What happens is that as soon as you apply self.effect2 on subWidget, self.effect is removed from it and actually deleted.发生的情况是,一旦您在 subWidget 上应用self.effect2self.effect就会从中删除并实际删除。 In PyQt terms, it means that the python object still exists, but not its C++ counterpart.在 PyQt 术语中,这意味着 python对象仍然存在,但不是它的 C++ 对应物。

UPDATE更新

It seems that you still don't understand how a QGraphicsEffect works.看来你还是不明白 QGraphicsEffect 是如何工作的。 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.它应用于底层小部件(在本例中为subWidget ),并且仅应用于使用小部件的几何形状指定的矩形。 You could even set the effectRect to any rect you want, even without any other widgets other than subWidget .您甚至可以将effectRect设置为您想要的任何矩形,即使没有除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.如果您需要将效果应用于多个矩形,那么您应该使用setClipRegion并使用复合 QRegion。
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.假设您将始终使用 QWidgets 作为效果的参考,并且该效果将始终应用于占据整个窗口区域的小部件,您可以使用需要跟踪的小部件的“观察列表”,并且每当他们的几何变化时更新效果。

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;您必须将watchWidget用于您想要查看效果的任何小部件,包括 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;显然,不再有setEffectRect了;
  • 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;使用此实现,如果所有被监视的小部件被隐藏或它们的几何形状为空,则效果会自动禁用自身,这意味着您不再需要调用self.effect.setEnabled()
  • even in this case (no watched widgets visible), you can still enable the effect to the whole area by explicitly calling setEnabled(True);即使在这种情况下(看不到小部件),您仍然可以通过显式调用 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.最后,我强烈建议你仔细研究这段代码(以及之前的)和关于QGraphicsEffectQPainter的文档(包括剪辑部分和所有相关页面),并自己创建一些简单的测试和示例,以更好地理解它们是如何实现的工作,然后再尝试做您想要实现的目标。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM