如何在 PyQt5 中显示带有效果的自定义 Qwidget

How to display a custom Qwidget with an effect in PyQt5

我正在 PyQt5 中开发一个应用程序,它由侧边菜单(左侧)和内容 window(右侧)组成:

在侧面菜单上,我有一个 设置 QPushButton。单击时,新的 window 出现在两者之间:

我想为这个 window 的显示设置动画,以便 :

到目前为止,这是我的代码:

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

import sys


class Settings(QWidget):
    def __init__(self):
        super().__init__()

        self.settingsFrame = QFrame(self)
        self.settingsFrame.setMaximumWidth(300)
        self.settingsFrame.setObjectName("Settings")

        self.settingsLayout = QVBoxLayout(self.settingsFrame)
        self.settingsLayout.setContentsMargins(0, 0, 0, 0)
        self.settingsLayout.setSpacing(0)

        self.settingsLayout.addWidget(QLabel("My settings info 1"))
        self.settingsLayout.addWidget(QLabel("My settings info 2"))
        self.settingsFrame.hide()


class LeftMenu(QWidget):
    def __init__(self, settings_w):
        super().__init__(settings_w)
        self.settings_w = settings_w

        self.leftMenuFrame = QFrame(self)
        self.leftMenuFrame.setMaximumWidth(200)
        self.leftMenuFrame.setObjectName("LeftMenu")

        self.menuLayout = QVBoxLayout(self.leftMenuFrame)
        self.menuLayout.setContentsMargins(0, 0, 0, 0)
        self.menuLayout.setSpacing(0)

        self.menuLayout.addWidget(QLabel("Some informations"))
        self.menuLayout.addWidget(QLabel("Some more"))
        self.menuLayout.addStretch(1)
        self.but = QPushButton("Settings")
        self.but.clicked.connect(self.handle_settings)
        self.menuLayout.addWidget(self.but)

    def handle_settings(self):
        if self.settings_w.isHidden():
            self.settings_w.settingsFrame.show()
        else:
            self.settings_w.settingsFrame.hide()


class Window(QWidget):
    def __init__(self):
        super().__init__()

        self.windowFrame = QFrame(self)
        self.windowLayout = QGridLayout(self.windowFrame)
        self.windowLayout.setContentsMargins(0, 0, 0, 0)
        self.windowLayout.setSpacing(0)

        self.label = QLabel("CONTENT")
        self.settings = Settings()
        self.leftMenu = LeftMenu(self.settings)

        self.windowLayout.addWidget(self.leftMenu.leftMenuFrame, 0, 0, 1, 1)
        self.windowLayout.addWidget(self.settings.settingsFrame, 0, 1, 1, 1)
        self.windowLayout.addWidget(self.label, 0, 2, 1, 1, Qt.AlignCenter)

        self.setLayout(self.windowLayout)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())

我想我应该使用 QPropertyAnimation,但我无法在我的示例中使用它。

感谢您的帮助!

EDIT :正如@ekhumoro 所建议的,这是我尝试使用 QPropertyAnimation

        if self.settings_w.isHidden():
            # self.settings_w.settingsFrame.show()
            animation = QPropertyAnimation(self.settings_w.settingsFrame, b"geometry")
            animation.setDuration(400)
            animation.setStartValue(QRect(0, 0, 0, self.settings_w.settingsFrame.height()))
            animation.setEndValue(QRect(0, 0, 300, self.settings_w.settingsFrame.height()))
            animation.start()

您尝试使用动画的主要问题是您将其创建为局部变量:一旦 handle_settings returns,动画就会被垃圾收集(删除),其他任何东西都不会发生了。

不幸的是,这还不够,因为还有其他问题。

首先,你的代码从对象结构的角度来看有些问题:SettingsLeftMenu类都不是真的 使用,因为您实际使用的是在 __init__ 中创建的小部件。这不仅在概念上是错误的,而且还会使您的代码不必要地复杂且难以处理。

然后还有另一个问题:显示以前隐藏的小部件会使其父布局立即根据新的大小调整所有其他小部件(和嵌套布局,如果有的话)显示的小部件,以及您尝试手动设置几何图形的事实不会改变任何内容,因为布局不受手动几何图形更改的影响。

解决方案(除了正确构造 类 之外)是为动画使用子菜单的固定宽度,并手动设置 布局 的几何形状有了它。

请注意,这没有考虑可调整大小的子菜单的可能性,这会使事情变得更加复杂,因为动画应该考虑所有其他同级小部件的大小提示以正确设置 几何的结束 值。在这种情况下,我只使用了所提供示例中建议的固定宽度。

class Settings(QWidget):
    def __init__(self):
        super().__init__()
        self.setFixedWidth(0)

        self.settingsLayout = QVBoxLayout(self)
        self.settingsLayout.setContentsMargins(0, 0, 0, 0)
        self.settingsLayout.setSpacing(0)

        self.settingsLayout.addWidget(QLabel("My settings info 1"))
        self.settingsLayout.addWidget(QLabel("My settings info 2"))

        self.animation = QVariantAnimation(self)
        self.animation.setDuration(400)
        self.animation.setStartValue(0)
        self.animation.setEndValue(300)
        self.animation.valueChanged.connect(self.updateSize)

    def updateSize(self, width):
        self.setFixedWidth(width)
        self.settingsLayout.setGeometry(QRect(width - 300, 0, 300, self.height()))

    def toggle(self):
        if self.width() and self.animation.direction() == self.animation.Forward:
            self.animation.setDirection(self.animation.Backward)
        else:
            self.animation.setDirection(self.animation.Forward)
        self.animation.start()


class LeftMenu(QFrame):
    toggle_settings = pyqtSignal()
    def __init__(self):
        super().__init__()
        self.setObjectName("LeftMenu")

        self.menuLayout = QVBoxLayout(self)
        self.menuLayout.setContentsMargins(0, 0, 0, 0)
        self.menuLayout.setSpacing(0)

        self.menuLayout.addWidget(QLabel("Some informations"))
        self.menuLayout.addWidget(QLabel("Some more"))
        self.menuLayout.addStretch(1)
        self.but = QPushButton("Settings")
        self.but.clicked.connect(self.toggle_settings)
        self.menuLayout.addWidget(self.but)

        self.setMaximumWidth(min(200, self.sizeHint().width()))


class Window(QWidget):
    def __init__(self):
        super().__init__()

        self.windowFrame = QFrame(self)
        self.windowLayout = QGridLayout(self.windowFrame)
        self.windowLayout.setContentsMargins(0, 0, 0, 0)
        self.windowLayout.setSpacing(0)

        self.label = QLabel("CONTENT")
        self.settings_w = Settings()
        self.leftMenu = LeftMenu()

        self.windowLayout.addWidget(self.leftMenu, 0, 0, 1, 1)
        self.windowLayout.addWidget(self.settings_w, 0, 1, 1, 1)
        self.windowLayout.addWidget(self.label, 0, 2, 1, 1, Qt.AlignCenter)

        self.setLayout(self.windowLayout)
        self.leftMenu.toggle_settings.connect(self.settings_w.toggle)

        self.setMinimumWidth(self.minimumSizeHint().width() + 300)