Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
432 views
in Technique[技术] by (71.8m points)

python - With PyQt5, how to add drop shadow effect on the rectangle area of a layout

I'm trying to implement a grid of images with titles under them, with a drop shadow on hover. What I've done so far is to add a drop shadow on the two widgets (the label with the image, and the label with the title), but I would like to have a drop shadow on the rectangular area that contains them. It tried to put them on another widget and apply the effect on this widget, but it still applies to both labels. Code below.

import sys, os
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *



class App(QMainWindow):
    def __init__(self):
        super().__init__()
        self.title = 'PyQt5 layout - pythonspot.com'
        self.left = 100
        self.top = 100
        self.width = 800
        self.height = 600
        
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        content_widget = QWidget()
        self.setCentralWidget(content_widget)
        self._lay = QGridLayout(content_widget)

        self.shadow = QGraphicsDropShadowEffect(self)
        self.shadow.setBlurRadius(5)
        
        nb = 6
        i = 0
        for i in range(0, 12):
            panel=QWidget()
            vbox = QVBoxLayout()
            pixmap = QPixmap(str(i+1)+"jpg")
            pixmap = pixmap.scaled(100, 150, transformMode=Qt.SmoothTransformation)
            img_label = QLabel(pixmap=pixmap)
            vbox.addWidget(img_label)
            
            txt_label = QLabel(str(i+1))
            vbox.addWidget(txt_label)
            vbox.addStretch(1)
            panel.setLayout(vbox)
            self._lay.addWidget(panel , int(i/nb), i%nb)
            panel.installEventFilter(self)
            i = i+1
            
        self.show()
        
    def eventFilter(self, object, event):
        if event.type() == QEvent.Enter:
            object.setGraphicsEffect(self.shadow)
            self.shadow.setEnabled(True)
        elif event.type() == QEvent.Leave:
            print("Mouse is not over the label")
            self.shadow.setEnabled(False)
        return False

if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

question from:https://stackoverflow.com/questions/65922309/with-pyqt5-how-to-add-drop-shadow-effect-on-the-rectangle-area-of-a-layout

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

The problem comes from the fact that a plain QWidget usually doesn't paint anything on its own: it's just a transparent widget. If you apply a graphics effect, it will be the result of what's inside that widget.

The solution is to ensure that the widget is opaque, by calling setAutoFillBackground(True).

Unfortunately, especially in your case, the result won't be very good, because you've lots of other widgets and a certain amount of spacing between them. You'll end up having the shadow behind everything:

shadow behind everything

The solution would be to call raise_() whenever the graphics effect is set, in order to ensure that the widget is actually above anything else (among the siblings and subchildren of its parent, obviously).
Unfortunately - again - this has a small but important issue, related to your implementation: the first time the effect is removed from a widget because it's set on another, the surrounding widgets don't get updated correctly.

artifacts

This is mostly due to the optimizations of the paint engine and the implementation of the graphics effect.

To avoid this issue, there are two possibilities:

  1. set an unique graphics effect for each widget, disabled upon creation, and then enable it only on the enterEvent:
class App(QMainWindow):
    def __init__(self):
        # ...
        for i in range(0, 12):
            panel = QWidget()
            effect = QGraphicsDropShadowEffect(panel, enabled=False, blurRadius=5)
            panel.setGraphicsEffect(panel)
            # ...

    def eventFilter(self, obj, event):
        if event.type() == QEvent.Enter and obj.graphicsEffect():
            obj.graphicsEffect().setEnabled(True)
        elif event.type() == QEvent.Leave and obj.graphicsEffect():
            obj.graphicsEffect().setEnabled(False)
        return super().eventFilter(obj, event)
  1. alternatively, get the bounding rect of the graphics effect, translate it to the coordinates of the widget, check if any other "sibling" geometry intersects the bounding rect and eventually call update() on those widgets:
class App(QMainWindow):
    def __init__(self):
        # ...
        self.panels = []
        for i in range(0, 12):
            panel = QWidget()
            self.panels.append(panel)
            # ...

    def eventFilter(self, obj, event):
        if event.type() == QEvent.Enter:
            obj.setGraphicsEffect(self.shadow)
            self.shadow.setEnabled(True)
            obj.raise_()
        elif event.type() == QEvent.Leave:
            obj.graphicsEffect().setEnabled(False)
            rect = self.shadow.boundingRect().toRect()
            rect.translate(obj.geometry().topLeft())
            for other in self.panels:
                if other != obj and other.geometry().intersects(rect):
                    other.update()
        return super().eventFilter(obj, event)

PS: object is a built-in type of Python, you should not use it as a variable.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...