1

I have the QSplitter widget in my Qt5 GUI, and there are two child widgets added to that splitter. and there is a QPushButton widget, when that button is pushed second child disappears and when again that button is pushed that child widget appears. but I want like expanding and collapsing of that child widget. but I have no idea how to animate the event when 'setSizes()' is called on the splitter widget.

can anybody provide me the code for that in pyqt5?

Here is my code

from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(QtWidgets.QMainWindow):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(400, 300)
        self.centralWidget = QtWidgets.QWidget(MainWindow)
        self.centralWidget.setObjectName("centralWidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralWidget)
        self.verticalLayout.setContentsMargins(11, 11, 11, 11)
        self.verticalLayout.setSpacing(6)
        self.verticalLayout.setObjectName("verticalLayout")

        self.pushButton = QtWidgets.QPushButton(self.centralWidget)
        self.pushButton.setObjectName("pushButton")
        self.pushButton.clicked.connect(self.splitter_resize)
        self.pushButton.setCheckable(True)

        self.verticalLayout.addWidget(self.pushButton)

        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setSpacing(6)
        self.horizontalLayout.setObjectName("horizontalLayout")

        self.pushButton_2 = QtWidgets.QPushButton(self.centralWidget)
        self.pushButton_2.setObjectName("pushButton_2")

        self.pushButton_3 = QtWidgets.QPushButton(self.centralWidget)
        self.pushButton_3.setObjectName("pushButton_3")

        self.splitter = QtWidgets.QSplitter()
        self.splitter.setOrientation(QtCore.Qt.Horizontal)
        self.splitter.addWidget(self.pushButton_2)
        self.splitter.addWidget(self.pushButton_3)

        self.horizontalLayout.addWidget(self.splitter)

        self.verticalLayout.addLayout(self.horizontalLayout)
        MainWindow.setCentralWidget(self.centralWidget)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def splitter_resize(self):
        if self.pushButton.isChecked():
            self.splitter.setSizes([16777215, 0])
            self.pushButton.setChecked = False
        else:
            self.splitter.setSizes([16777215, 16777215])
            self.pushButton.setChecked = True

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton.setText(_translate("MainWindow", "PushButton"))
        self.pushButton_2.setText(_translate("MainWindow", "PushButton"))
        self.pushButton_3.setText(_translate("MainWindow", "PushButton"))

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241

1 Answers1

4

You can use QVariantAnimation:

from PyQt5 import QtCore, QtGui, QtWidgets


MIN_SIZE, MAX_SIZE = 0, 16777215


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        button = QtWidgets.QPushButton(
            "Press me", checkable=True, toggled=self.onToggled
        )
        left_button = QtWidgets.QPushButton("Left")
        right_button = QtWidgets.QPushButton("Right")

        sp = right_button.sizePolicy()
        sp.setHorizontalPolicy(QtWidgets.QSizePolicy.Ignored)
        right_button.setSizePolicy(sp)

        self.m_splitter = QtWidgets.QSplitter(orientation=QtCore.Qt.Horizontal)
        self.m_splitter.addWidget(left_button)
        self.m_splitter.addWidget(right_button)

        widget = QtWidgets.QWidget()
        self.setCentralWidget(widget)

        lay = QtWidgets.QVBoxLayout(widget)
        lay.addWidget(button)
        lay.addWidget(self.m_splitter)

        self.m_animation = QtCore.QVariantAnimation(
            self,
            startValue=MAX_SIZE,
            endValue=MIN_SIZE,
            valueChanged=self.onValueChanged,
            duration=1000,
            easingCurve=QtCore.QEasingCurve.InOutCubic,
        )

    @QtCore.pyqtSlot(bool)
    def onToggled(self, checked):
        self.m_animation.setDirection(
            QtCore.QAbstractAnimation.Forward
            if checked
            else QtCore.QAbstractAnimation.Backward
        )
        self.m_animation.start()

    @QtCore.pyqtSlot(QtCore.QVariant)
    def onValueChanged(self, value):
        s = [MAX_SIZE, value]
        self.m_splitter.setSizes(s)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.resize(640, 480)
    w.show()
    sys.exit(app.exec_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Thank you very much. it helped me a lot. I learned a new thing today. – Ankit Kumar Verma Jun 28 '19 at 12:22
  • in this animation collapsing always start from the middle of the splitter, even though the child widgets are not equally partitioned i.e. splitter handler is not at the middle. how can it be resolved? I have used `right_button.width()` as start value and `0` as the end value for a separate animation but it is not working, obviously, I have removed `Forward` animation – Ankit Kumar Verma Jun 28 '19 at 12:49
  • @AnkitKumarVerma In your [MRE] the buttons have the same weight, in addition the animation starts from the center to the right when you press the button, and if you press the button back starts from the right to the center. If you have another configuration then your MRE is not correct, so improve it and then I will provide you with an adequate solution, the forward and backward serves to indicate the direction, if you remove it as you have done, there will only be collapse or expansion that starts from a same point, so do not eliminate it. Read [ask] – eyllanesc Jun 28 '19 at 13:04
  • Actually, I removed the `Forward` animation because I have added it separately as I said above. – Ankit Kumar Verma Jun 28 '19 at 13:07
  • @AnkitKumarVerma Well if you modify my code my solution will not work, if you have other requirements I invite you to create a new question providing an MRE and clearly pointing out the new requirements, and so the community will be happy to try to help you. Please read [ask] – eyllanesc Jun 28 '19 at 13:09
  • In splitter, any user can drag the splitter handle and It will always possible that child widgets will not be equally partitioned. I am developing software in which child widgets are equally partitioned initially but later on a user can change the configuration. So this is the scenario. so I think I didn't need to explicitly set another configuration. If a user drags the splitter handle then configuration always can be different. So I want the solution for that case. – Ankit Kumar Verma Jun 28 '19 at 13:14
  • @AnkitKumarVerma I need a more detailed explanation of what you want. Let's say that as you point out at the beginning the widgets have the same width and the user modifies the width, and then presses the button causing the right widget to collapse, in that moment the user can do 2 things: – eyllanesc Jun 28 '19 at 13:25
  • @AnkitKumarVerma 1) press the button and the Right widget expands, OR 2) You can modify the width and press the button again. In the first case it is understandable, in the second case it is not very clear: should the right widget be collapsed since it has a width> 0 or should it restore its status like the one it had at the beginning after the width modification? – eyllanesc Jun 28 '19 at 13:25
  • @AnkitKumarVerma In addition to this I will propose another extreme case: the user dragged the handle causing the right widget to be collapsed. If the button is pressed that must happen with the width? – eyllanesc Jun 28 '19 at 13:29
  • the right widget should be collapsed when its width>0 but it should be collapsed from its current width, not from the middle of the splitter. your code is collapsing the right widget from the middle of the splitter, without considering the current width. – Ankit Kumar Verma Jun 28 '19 at 13:43
  • I have considered that case when user has collapsed the right widget by dragging the handler. I am handlling that situation separately. – Ankit Kumar Verma Jun 28 '19 at 13:46
  • @AnkitKumarVerma I'm going to put different cases and point out for each case what should happen: 1) let's say that the widths are [40, 60] and pressing the button will then be at [100, 0] but the user manually moves to [80, 20], If the user presses the button What should be the new widths: [100, 0] or [40, 60] or [80, 20] ?. 2) If the user moves until the width is [100, 0] and the user presses the button What should be the new ratio of widths? If you answer both questions then I could propose a solution. Situations can not be handled separately since they are related. – eyllanesc Jun 28 '19 at 13:49
  • 1) if width [40, 60] then [100, 0] and if width [80, 20] then [100, 0] or more generally if width [x, y] x>0 and y>0 then [100, 0] 2) width [100, 0] then [50, 50] – Ankit Kumar Verma Jun 28 '19 at 14:28
  • @eyllanesc this is such an awesome widget – nathancy Jun 29 '19 at 01:24
  • what happened @eyllanesc – Ankit Kumar Verma Jul 02 '19 at 09:30