简体   繁体   中英

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

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:

一切背后的阴影

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.

人工制品

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.

self.shadow = QGraphicsDropShadowEffect(self)
self.shadow.setBlurRadius(15)
self.shadow.setXOffset(0)
self.shadow.setYOffset(0)
self.shadow.setColor(QColor(0, 0, 0, 150))
# add the shadow object to the frame
self.ui.topframe.raise_()
self.ui.topframe.setGraphicsEffect(self.shadow)

enter image description here

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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