Search code examples
python-3.xqtpyside6

How to Create Toggle Frame by using PySide6?


I've created windows form in pyside6. It has a master frame and console frame. There is a spliter to move console frame (Up and Down) and a (toggle) button.

When I click the Toggle button, the console frame show and if I click again , its hide the frame.

this is the design part:

from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
    QMetaObject, QObject, QPoint, QRect,
    QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
    QFont, QFontDatabase, QGradient, QIcon,
    QImage, QKeySequence, QLinearGradient, QPainter,
    QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QFrame, QHBoxLayout, QMainWindow,
    QPushButton, QSizePolicy, QSplitter, QTextEdit,
    QVBoxLayout, QWidget)

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        if not MainWindow.objectName():
            MainWindow.setObjectName(u"MainWindow")

        MainWindow.setEnabled(True)
        MainWindow.resize(1234, 905)
        MainWindow.setStyleSheet(u"background-color: rgb(58, 58, 102);")
        MainWindow.setUnifiedTitleAndToolBarOnMac(True)

        self.centralwidget = QWidget(MainWindow)
        self.centralwidget.setObjectName(u"centralwidget")

        self.verticalLayout = QVBoxLayout(self.centralwidget)
        self.verticalLayout.setObjectName(u"verticalLayout")

        self.splitter = QSplitter(self.centralwidget)
        self.splitter.setObjectName(u"splitter")
        self.splitter.setMinimumSize(QSize(0, 4))
        self.splitter.setStyleSheet(u"")
        self.splitter.setOrientation(Qt.Vertical)

        self.master_frame = QFrame(self.splitter)
        self.master_frame.setObjectName(u"master_frame")

        sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(self.master_frame.sizePolicy().hasHeightForWidth())

        self.master_frame.setSizePolicy(sizePolicy)
        self.master_frame.setMaximumSize(QSize(16777215, 16777215))
        self.master_frame.setFrameShape(QFrame.StyledPanel)
        self.master_frame.setFrameShadow(QFrame.Raised)

        self.horizontalLayout = QHBoxLayout(self.master_frame)
        self.horizontalLayout.setSpacing(0)
        self.horizontalLayout.setObjectName(u"horizontalLayout")
        self.horizontalLayout.setContentsMargins(0, -1, 0, 0)

        self.visual_frame = QFrame(self.master_frame)
        self.visual_frame.setObjectName(u"visual_frame")
        self.visual_frame.setFrameShape(QFrame.StyledPanel)
        self.visual_frame.setFrameShadow(QFrame.Raised)

        self.horizontalLayout_2 = QHBoxLayout(self.visual_frame)
        self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")

        self.textEdit = QTextEdit(self.visual_frame)
        self.textEdit.setObjectName(u"textEdit")
        self.textEdit.setStyleSheet(u"background-color: rgb(52, 52, 52); color: rgb(255, 255, 255);")

        self.horizontalLayout_2.addWidget(self.textEdit)

        self.horizontalLayout.addWidget(self.visual_frame)

        self.ctrl_frame = QFrame(self.master_frame)
        self.ctrl_frame.setObjectName(u"ctrl_frame")
        self.ctrl_frame.setMinimumSize(QSize(250, 0))
        self.ctrl_frame.setMaximumSize(QSize(250, 16777215))
        self.ctrl_frame.setFrameShape(QFrame.StyledPanel)
        self.ctrl_frame.setFrameShadow(QFrame.Raised)

        self.btn_toggle = QPushButton(self.ctrl_frame)
        self.btn_toggle.setObjectName(u"btn_toggle")
        self.btn_toggle.setGeometry(QRect(40, 450, 171, 81))

        font = QFont()
        font.setPointSize(19)

        self.btn_toggle.setFont(font)
        self.btn_toggle.setCursor(QCursor(Qt.ArrowCursor))
        self.btn_toggle.setStyleSheet(u"color: rgb(255, 255, 255);")

        self.horizontalLayout.addWidget(self.ctrl_frame)

        self.splitter.addWidget(self.master_frame)

        self.console_frame = QFrame(self.splitter)
        self.console_frame.setObjectName(u"console_frame")

        sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
        sizePolicy1.setHorizontalStretch(0)
        sizePolicy1.setVerticalStretch(0)
        sizePolicy1.setHeightForWidth(self.console_frame.sizePolicy().hasHeightForWidth())

        self.console_frame.setSizePolicy(sizePolicy1)
        self.console_frame.setMinimumSize(QSize(0, 0))
        self.console_frame.setMaximumSize(QSize(16777215, 16777215))
        self.console_frame.setBaseSize(QSize(0, 0))
        self.console_frame.setFrameShape(QFrame.StyledPanel)
        self.console_frame.setFrameShadow(QFrame.Raised)

        self.verticalLayout_4 = QVBoxLayout(self.console_frame)
        self.verticalLayout_4.setObjectName(u"verticalLayout_4")
        self.verticalLayout_4.setContentsMargins(0, 0, 0, 0)

        self.textEdit_console = QTextEdit(self.console_frame)
        self.textEdit_console.setObjectName(u"textEdit_console")
        self.textEdit_console.setMinimumSize(QSize(0, 0))

        font1 = QFont()
        font1.setPointSize(12)

        self.textEdit_console.setFont(font1)
        self.textEdit_console.viewport().setProperty("cursor", QCursor(Qt.IBeamCursor))
        self.textEdit_console.setStyleSheet(u"background-color: #282C34; color: rgb(0, 255, 0);")

        self.verticalLayout_4.addWidget(self.textEdit_console)

        self.splitter.addWidget(self.console_frame)

        self.verticalLayout.addWidget(self.splitter)

        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)

        QMetaObject.connectSlotsByName(MainWindow)
    # setupUi

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"Toggle Frame", None))
        self.btn_toggle.setText(QCoreApplication.translate("MainWindow", u"Toggle", None))
    # retranslateUi

this is the function class:

from PySide6.QtWidgets import QMainWindow


class UIFunctions(QMainWindow):
    def __init__(self, ui_main_window):
        super(UIFunctions, self).__init__()

        self.oldPosition = None
        self.ui = ui_main_window
        self.ui.setupUi(self)

        # Console Resize initial
        self.setup_console_resize_button()

    def setup_console_resize_button(self):
        self.ui.btn_toggle.clicked.connect(self.toggle_console_height)

    def toggle_console_height(self):
        console_frame = self.ui.console_frame
        current_height = console_frame.height()

        if current_height == 0:
            # If the console is hidden, calculate the new height
            new_height = 220
            self.ui.master_frame.setMaximumHeight(self.ui.master_frame.maximumHeight() + new_height)
        else:
            # If the console is visible, set the new height to 0
            new_height = 0
            self.ui.master_frame.setMaximumHeight(self.ui.master_frame.maximumHeight() - current_height)

        # Set the size of the console frame explicitly
        console_frame.setMinimumHeight(new_height)
        console_frame.setMaximumHeight(new_height)

        # Calculate splitter sizes
        total_height = self.ui.master_frame.maximumHeight() + self.ui.console_frame.maximumHeight()
        master_frame_ratio = self.ui.master_frame.maximumHeight() / total_height
        console_frame_ratio = self.ui.console_frame.maximumHeight() / total_height

        # Set splitter sizes
        self.ui.splitter.setSizes([int(master_frame_ratio * total_height), int(console_frame_ratio * total_height)])

main method :

import sys
import platform
import PySide6
from PySide6 import QtCore, QtGui, QtWidgets
from PySide6.QtCore import (QCoreApplication, QPropertyAnimation, QDate, QDateTime, QMetaObject, QObject, QPoint, QRect,
                            QSize, QTime, QUrl, Qt, QEvent)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, QFont, QFontDatabase, QIcon, QKeySequence,
                           QLinearGradient, QPalette, QPainter, QPixmap, QRadialGradient)
from PySide6.QtWidgets import *
from PySide6.QtWidgets import QApplication

from ui_design import Ui_MainWindow
from ui_functions import UIFunctions


class MainWindow(UIFunctions):
    def __init__(self):
        super(MainWindow, self).__init__(Ui_MainWindow())


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

but once if i click the toggle button, then the spliter not working properlly !

can you solve the problem ?


Solution

  • A splitter can only resize a dimension that coincides with its orientation, and within the widget size limits: its minimum and maximum length. If the splitter is horizontal, it can only resize the widget between its minimumWidth() and maximumWidth(), eventually considering its minimumSizeHint() in case of collapsing (hiding the widget whenever it's too small, or showing it again if there's enough space).

    That QSplitter doesn't work anymore because doing setMinimumHeight(new_height) and setMaximumHeight(new_height) is like doing setFixedHeight(new_height), and a vertical splitter cannot change that height.

    Since you're also setting the item sizes of the splitter, you should only set the maximum height of the "console", not the minimum (which is implicitly 0). Also note that you should not set an arbitrary maximum height unless one was previously set (which doesn't seem the case), otherwise you would limit the possibility to expand it after toggling, even if it was previously possible.
    You should instead restore the default maximum height (QtWidgets.QWIDGETSIZE_MAX, 16777215) and properly compute the ratios using the currently suggested height, not the maximum, and based on the current height of the splitter container.

    Besides, in reality, all this is quite pointless.
    If all you want is to toggle the visibility of a widget, just use the proper function, which is exactly what QSplitter normally does, and is achieved by calling the basic setVisible() QWidget function, against a negation of the current value of that same property:

        def toggle_console_height(self):
            self.ui.console_frame.setVisible(
                not self.ui.console_frame.isVisible()
            )
    

    And remove all the remaining code, which is unnecessary.