Как применить BlurEffect (QtWidgets.QGraphicsBlurEffect) дважды? - PullRequest
0 голосов
/ 10 марта 2020

У меня есть этот код

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

Мне нужно иметь возможность применить эффект размытия к нескольким виджетам одновременно

И проблема в том, когда я добавляю

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

после

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

Я получаю эту ошибку

Traceback (последний вызов был последним): файл "C: \ Users \ пользователь \ 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 () RuntimeError: упакованный объект C / C ++ типа BlurEffect был удален

Кто-нибудь знает, как это исправить?

photoshop enter image description here

1 Ответ

1 голос
/ 10 марта 2020

Вы не можете. Только один эффект может быть применен сразу к виджету (и после этого, ни один из эффектов не может быть применен ни к одному из его дочерних элементов или родителей), по крайней мере, для QWidgets.

From QWidget.setGraphicsEffect () :

Если на этот виджет уже установлен эффект, QWidget удалит существующий эффект перед установкой нового эффекта.

Что происходит, так это то, что как только вы применяете self.effect2 к подвиджету, self.effect удаляется из него и фактически удаляется. В терминах PyQt это означает, что python объект все еще существует, но не его аналог C ++.

UPDATE

Кажется, что вы все еще не понимаете, как QGraphicsEffect работает. Эффект NOT применяется к виджетам, которые вы видите на размытом фоне. Он применяется к базовому виджету (в данном случае subWidget) и только к прямоугольнику (ам), указанным с использованием геометрии виджетов. Вы можете даже установить effectRect на любой нужный вам прямоугольник, даже без каких-либо других виджетов, кроме subWidget.

Если вам нужно применить эффект к нескольким прямоугольникам, вам следует использовать setClipRegion и используйте с ним составной QRegion.
Предполагая, что вы всегда будете использовать 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)

Важные примечания:

  • вам необходимо используйте watchWidget для любого виджета, для которого вы хотите увидеть эффект, включая topMenu; опять же, это не означает, что эффект применяется к этим виджетам, но для этого используется их геометрия;
  • , очевидно, больше нет setEffectRect;
  • с этой реализацией Эффект отключается автоматически, если все отслеживаемые виджеты скрыты или их геометрия равна нулю, что означает, что вам больше не нужно вызывать self.effect.setEnabled();
  • даже в этом случае (видимые виджеты не видны), вы все еще можно включить эффект для всей области , явно вызвав setEnabled (True);

Наконец, я настоятельно рекомендую вам внимательно изучить этот код (и предыдущий) и документацию о QGraphicsEffect и QPainter (включая раздел clipping и все связанные страницы), а также о создании простых тестов и примеров по чтобы лучше понять, как они работают, прежде чем пытаться делать то, чего вы пытаетесь достичь.

...