С помощью PyQt5, как добавить эффект тени в прямоугольную область макета

#python #pyqt5

#python #pyqt5

Вопрос:

Я пытаюсь реализовать сетку изображений с заголовками под ними, с тенью при наведении курсора мыши. Что я сделал до сих пор, так это добавил тень на два виджета (ярлык с изображением и ярлык с заголовком), но я хотел бы иметь тень на прямоугольной области, которая их содержит. Он попытался поместить их в другой виджет и применить эффект к этому виджету, но он по-прежнему применяется к обеим меткам. Код ниже.

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

 

Комментарии:

1. Вы имеете в виду, что вам нужна тень для всего контейнера ( panel ), например? i.stack.imgur.com/gnWL8.png

2. @musicamante Да, это то, что я имею в виду

3. Есть ли конкретная причина для того, чтобы не устанавливать отдельные graphicseffect для всех виджетов?

Ответ №1:

Проблема заключается в том, что обычный QWidget обычно сам по себе ничего не рисует: это просто прозрачный виджет. Если вы примените графический эффект, он будет результатом того, что находится внутри этого виджета.

Решение состоит в том, чтобы убедиться, что виджет непрозрачен, путем вызова setAutoFillBackground(True) .

К сожалению, особенно в вашем случае, результат будет не очень хорошим, потому что у вас много других виджетов и определенное расстояние между ними. В итоге у вас будет тень за всем:

тень за всем

Решением было бы вызывать raise_() всякий раз, когда установлен графический эффект, чтобы убедиться, что виджет на самом деле выше всего остального (среди братьев и сестер и дочерних элементов его родителя, очевидно).
К сожалению, опять же, у этого есть небольшая, но важная проблема, связанная с вашей реализацией: в первый раз, когда эффект удаляется из виджета, потому что он установлен на другом, окружающие виджеты не обновляются правильно.

артефакты

В основном это связано с оптимизацией движка рисования и реализацией графического эффекта.

Чтобы избежать этой проблемы, есть две возможности:

  1. установите уникальный графический эффект для каждого виджета, отключенный при создании, а затем включите его только на 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. в качестве альтернативы, получите ограничивающий прямоугольник графического эффекта, переведите его в координаты виджета, проверьте, пересекает ли какая-либо другая «родственная» геометрия ограничивающий прямоугольник и, в конечном итоге, вызовите update() эти виджеты:
 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 это встроенный тип Python, вы не должны использовать его в качестве переменной.

Комментарии:

1. Спасибо за вашу помощь! Первое решение не сработало для меня, у меня продолжает появляться эта странная перекрестная тень справа внизу, даже если между виджетами есть некоторое пространство. Однако второй вариант работал намного лучше. Я не уверен в этом, но кажется, что смещение применяется только к нижней правой части тени, но не эффективно в начале, я имею в виду, что тень начинается слишком близко к виджету. Также есть пробел под заголовком. Я понимаю, что это происходит из-за растянутого пространства, которое я добавил, чтобы заголовок прикреплялся к изображению. Я думаю, мне придется использовать другой макет, чем вертикальный прямоугольник.

2. Может ли другое решение прийти из QML? Я наткнулся на это: doc.qt.io/qt-5/qml-qtgraphicaleffects-rectangularglow.html Но не мог понять, как им пользоваться, и тоже не нашел ни одного полезного документа.

3. @Benco смещение является свойством эффекта (см. Документацию ), и по умолчанию оно составляет 8 пикселей в правом нижнем углу.