简体   繁体   中英

How to display a custom Qwidget with an effect in PyQt5

I am working on an application in PyQt5 which is composed of a side menu (on the left) and a content window (on the right):

初始窗口

On the side menu, I have a Settings QPushButton . On click, a new window appears between the two:

点击设置后的窗口

I would like to animate the display of this window so that:

  • when it appears, it sweeps from left to right;
  • when it disappears, it sweeps from right to left.

Here's my code so far:

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

I think I should use QPropertyAnimation but I can't make it work with my example.

Thanks for your help !

EDIT : as suggested by @ekhumoro, Here's what I tried with 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()

The main problem with your attempt with the animation is that you created it as a local variable: as soon as handle_settings returns, the animation gets garbage collected (deleted) and nothing else will happen.

Unfortunately, that won't be enough, as there are other problems.

First of all, your code as some problems from the point of view of object structure: both Settings and LeftMenu classes are not really used, as you're actually using the widgets created in their __init__ . Not only this is conceptually wrong, but it also makes your code unnecessarily convoluted and difficult to deal with.

Then there's another issue: showing a previously hidden widget makes its parent layout to instantly resize all other widgets (and nested layout, if any) based on the size of the newly shown widget, and the fact that you are trying to manually set the geometry doesn't change anything, as the layout is not affected by manual geometry changes.

The solution (besides properly structuring the classes) is to use the fixed width of the submenu for the animation, and manually set the geometry of the layout along with it.

Note that this doesn't consider the possibility of a resizable submenu, which would make things much more complex, as the animation should account for the size hints of all other sibling widgets to properly set the end value for the geometry. In this case, I only used the fixed width suggested in the provided example.

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)

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