有没有办法把阴影放在 QSplashScreen 后面?

Is there a way to put shadow just behind QSplashScreen?

我正在尝试制作一个项目,我想在我的 QSplashScreen 后面放置阴影。有办法吗?

我搜索了一下,QGraphicsDropShadowEffect 没有用。

阴影效果通常是 OS 的责任。

对于那些效果不可用的系统,您可以创建“虚拟”光栅化阴影,但也有一些缺点:

  • 如果系统支持阴影效果,结果会很难看,因为系统阴影会随着自定义阴影一起绘制;
  • 阴影效果应用于整个“根”的外观window,因此如果在显示启动画面时某些内容发生变化,这些变化将不会反映在阴影效果中;
  • 如果系统不支持不透明移动事件(意味着moveEvent()仅在移动完成时发送),阴影效果会显示一些伪像;

这是显示阴影效果的 QSplashScreen 的可能重新实现。诀窍是创建一个临时的 QGraphicsScene 并添加一个 QGraphicsRectItem,它应用了 QGraphicsDropShadowEffect。

class SplashTest(QtWidgets.QSplashScreen):
    _shadowSize = 8
    shadowCache = None
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setWindowFlags(QtCore.Qt.SplashScreen)
        self.desktopPixmap = QtWidgets.QApplication.primaryScreen().grabWindow(0)

    @QtCore.pyqtProperty(int)
    def shadowSize(self):
        return self._shadowSize

    @shadowSize.setter
    def shadowSize(self, shadowSize):
        if shadowSize == self._shadowSize:
            return
        self._shadowSize = shadowSize
        if self.shadowCache is None:
            self.shadowCache = {}
        else:
            self.shadowCache.clear()
        self.repaint()

    def setShadowSize(self, shadowSize):
        self.shadowSize = shadowSize

    def setPixmap(self, pixmap):
        self.shadowCache.clear()
        super().setPixmap(pixmap)
        size = QtCore.QSize(pixmap.width() + self.shadowSize, pixmap.height() + self.shadowSize)
        self.setFixedSize(size)
        if self.isVisible():
            self.repaint()

    def drawShadow(self, event):
        if not self.pixmap() or self.pixmap().isNull():
            return
        shadowCache = self.shadowCache.get((self.width(), self.height()))
        if not shadowCache:
            # create the shadow effect if it's not cached yet
            self.shadowCache[(self.width(), self.height())] = shadowCache = QtGui.QPixmap(self.size())
            shadowCache.fill(QtCore.Qt.transparent)

            # create a virtual QGraphicsScene to paint onto
            scene = QtWidgets.QGraphicsScene()
            scene.setSceneRect(QtCore.QRectF(self.desktopPixmap.rect()))
            # add a QGraphicsRectItem to the scene that will be used for the shadow
            pmItem = scene.addRect(QtCore.QRectF(self.pixmap().rect()).translated(self.geometry().topLeft()))
            pmItem.setPen(QtGui.QPen(QtCore.Qt.NoPen))
            pmItem.setBrush(QtGui.QBrush(QtCore.Qt.white))
            offset = QtCore.QPoint(self.shadowSize / 2, self.shadowSize / 2)
            effect = QtWidgets.QGraphicsDropShadowEffect(blurRadius=self.shadowSize, offset=offset)
            pmItem.setGraphicsEffect(effect)

            pmPainter = QtGui.QPainter(shadowCache)
            pmPainter.setRenderHints(pmPainter.Antialiasing)
            scene.render(pmPainter, QtCore.QRectF(self.rect()), pmItem.sceneBoundingRect().adjusted(0, 0, self.shadowSize, self.shadowSize))
            pmPainter.end()

        qp = QtGui.QPainter(self)
        # draw the background taken from the root window
        qp.drawPixmap(event.rect(), self.desktopPixmap, event.rect().translated(self.pos()))
        # draw the shadow
        qp.drawPixmap(0, 0, shadowCache)

    def event(self, event):
        if event.type() == QtCore.QEvent.Paint:
            # intercept the Paint event in order to draw the shadow
            self.drawShadow(event)
        return super().event(event)

    def moveEvent(self, event):
        self.repaint()


def updateSplash():
    # remember: globals should *ALWAYS* be avoided unless you *really* know how to
    # use them; this is just for the purpose of this example
    global loadingStatus
    loadingStatus += 1
    splashTest.showMessage('Loading: {}%'.format(loadingStatus))
    if loadingStatus >= 100:
        timer.stop()
        QtCore.QTimer.singleShot(1000, lambda: splashTest.showMessage('Completed'))
        QtCore.QTimer.singleShot(5000, splashTest.close)

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    splashTest = SplashTest(shadowSize=16)
    splashTest.setPixmap(QtGui.QPixmap('splash.png'))
    loadingStatus = 0
    timer = QtCore.QTimer(interval=50, timeout=updateSplash)
    timer.start()
    splashTest.show()
    sys.exit(app.exec_())