PySide2, looking for a specific class

Heya everyone.

I’m currently working with PySide2 and I can’t manage to find the right class to build this type of UI.
A bar, when it’s opened it shows some more widgets, and when it is closed, it hides everything inside.
Typically what we can see within Maya’s UI.

UIex

I don’t know if it even exists;

Thanks for you help.

Luca.

1 Like

Think it’s something you will have to create, or at least I felt the need to long ago when I wanted something similar. Want to remember it was groupboxes that I allowed to toggle the contents of.

Something like this ( searched for “groupbox animated toggle qt” ) https://stackoverflow.com/questions/54760970/qt-layout-does-not-reflect-groupbox-size-change

Thank you for your answer.

Yes I thought about using QGroupBox, I did something similar recently, but I really liked the look of the given example from maya’s UI.

I will try to create it myself, and if I can’t manage to achieve the same look, I will stick with the groupBox then.

Thank you again for your response and have a nice week-end !

I believe this has what you are looking for https://stackoverflow.com/questions/32476006/how-to-make-an-expandable-collapsable-section-widget-in-qt/37927256#37927256

1 Like

This is it !! Thank you soo much for your help !!

+1 to just using groupbox

When styled correctly they look exactly like what you want and are much simpler to implement than the other alternatives.
eg
image

3 Likes

oooh !! I didn’t even knew it was possible to style the checkbox !!
Would I be able to change the size of the groupBox by using setGeometry() ? Or is there any better way to achive this “shrink” effect ?

Thank you for answers, it helps me a lot !!

Stylesheet for those groupBox is as follows

stylesheet
self.setStyleSheet(
    "QGroupBox {"
    "border: 1px solid;"
    "border-color: rgba(0, 0, 0, 64);"
    "border-radius: 6px;"
    "background-color: rgb(78, 80, 82);"
    "font-weight: bold;"
    "}"
    "QGroupBox::title {"
    "subcontrol-origin: margin;"
    "left: 6px;"
    "top: 4px;"
    "}"
    "QGroupBox::indicator:checked {"
    "image: url(:/arrowDown.png);"
    "}"
    "QGroupBox::indicator:unchecked {"
    "image: url(:/arrowRight.png);"
    "}"
)

The url(:/arrowRight.png) syntax is using the built in Maya Qt resource icons so will look a little different depending on which version of Maya you’re in. (and will need a different implementation outside of Maya)

I don’t use setGeometry() to achieve the dropdown effect, I just add one single (borderless) QWidget as a child of the groupBox, then add all other content to that widget. Connect the toggled signal of the groupBox to the setVisible slot of this widget. You will probably want to override sizeHint on the groupBox and also resize whatever is the parent of the groupBox when toggling.

4 Likes

I have done this before the hard way, where I built a custom widget and drew the arrow myself.

Example:

from PySide2 import QtWidgets, QtCore, QtGui

class Arrow(QtWidgets.QFrame):

    def __init__(self, parent=None):
        super(CollapsibleWidget.Arrow, self).__init__(parent=parent)
        
        # define polygon points when the arrow is facing different directions
        self._vertical_arrow_points = (QtCore.QPointF(7.0, 8.0), QtCore.QPointF(17.0, 8.0), QtCore.QPointF(12.0, 13.0))
        self._horizontal_arrow_points = (QtCore.QPointF(8.0, 7.0), QtCore.QPointF(13.0, 12.0), QtCore.QPointF(8.0, 17.0))

        self._current_points = self._horizontal_arrow_points 
        self.setFixedSize(24, 24)

    def setCollapsed(self, collapsed):
        if collapsed:
            self._current_points= self._horizontal_arrow_points 
        else:
            self._current_points= self._vertical_arrow_points 
        self.repaint()

    def paintEvent(self, event):
        painter = QtGui.QPainter(self)
        painter.begin(self)
        painter.setBrush(QtCore.Qt.white)
        painter.setPen(QtCore.Qt.white)
        painter.drawPolygon(self._current_points)
        painter.end()

Thought this would be helpful in some situations. If you want collapsible sections that look exactly like Maya’s, you have to build custom widgets. I was building a pretty complex UI so I preferred the cleanliness of the result that I was able to get with this method. The code above only gives you the little arrow though, so you still have to build the rest of it with QFrames and handle expanding layouts.

If you can use the group box method for your purposes I’d definitely recommend going that route because it’s less hassle and easier to maintain.

Hi, I found this thread and was looking into creating an expandable/collapsable tab. I’ve been tinkering with this for hours and can’t seem to get it just right.
I would have liked for the window to resize to fit just the three buttons when it is collapsed. Also the styleSheet isn’t really having an effect. Not sure where I’m going wrong. Tried different ways to write what I thought was the correct way to reference my QGroupBox, but nothing worked so I left it as it were.

from maya import OpenMayaUI as omui
from PySide2 import QtGui
from PySide2 import QtWidgets
from PySide2 import __version__
from shiboken2 import wrapInstance 

def maya_main_window():
    '''
    Return the Maya main window widget as a Python object
    '''
    main_window_ptr = omui.MQtUtil.mainWindow()
    return wrapInstance(long(main_window_ptr), QtWidgets.QWidget)
    
    
class TestTool(QtWidgets.QWidget):
    def __init__(self, parent=maya_main_window()):
        super(TestTool, self).__init__(parent)

        
    newNames = {}
    renameDict = {}
    state = 0
    
    def create(self):
        
        self.setWindowTitle("Test")
        self.setWindowFlags(QtCore.Qt.Tool)
        self.resize(300, 100) # re-size the window
        
        
        
        # create layout
        mainLayout = QtWidgets.QVBoxLayout()
        
        # widgets
        runButton1 = QtWidgets.QPushButton("Does nothing")
        runButton2 = QtWidgets.QPushButton("Does nothing")
        runButton3 = QtWidgets.QPushButton("Expand/Collapse")
        runButton3.clicked.connect(self.showGrpLayout)# connect button to rename method
        
        mainLayout.addWidget(runButton1)
        mainLayout.addWidget(runButton2)
        mainLayout.addWidget(runButton3)

        self.grpBox = QtWidgets.QGroupBox()
        self.grpBox.setVisible(0)
        
        #Create the group box layout:
        self.group_box_layout = QtWidgets.QVBoxLayout()
        
        #Add widgets to the group box layout like this:
        self.group_box_layout.addWidget(QtWidgets.QCheckBox("Check Box 1"))
        self.group_box_layout.addWidget(QtWidgets.QCheckBox("Check Box 2"))
        self.group_box_layout.addWidget(QtWidgets.QCheckBox("Check Box 3"))
        
        #Assign the group box layout to the group box:
        self.grpBox.setLayout(self.group_box_layout)
        #Assing the group box to the main layout:
        mainLayout.addWidget(self.grpBox)
        
        #And add this at the end:
        widget = QtWidgets.QWidget()
        
          
        self.setLayout(mainLayout)
        
        self.setStyleSheet(
        "QGroupBox {"
        "border: 1px solid;"
        "border-color: rgba(0, 0, 0, 64);"
        "border-radius: 6px;"
        "background-color: rgb(78, 80, 82);"
        "font-weight: bold;"
        "}"
        "QGroupBox::title {"
        "subcontrol-origin: margin;"
        "left: 6px;"
        "top: 4px;"
        "}"
        "QGroupBox::indicator:checked {"
        "image: url(:/arrowDown.png);"
        "}"
        "QGroupBox::indicator:unchecked {"
        "image: url(:/arrowRight.png);"
        "}"
        )

        # to force the widgets to attach to the top
        spaceItem = QtWidgets.QSpacerItem( 10, 10, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
        mainLayout.addSpacerItem(spaceItem)
        
    
    def showGrpLayout(self):
        self.state = int(not (bool(self.state)))
        self.grpBox.setVisible(self.state)
        # not sure how I can fetch the window and modify it
        self.resize(300, 100)
        


if __name__ == "__main__":
    try:
        ui.deleteLater()
    except:
        pass
    
    
    try:
        ui = TestTool()
        ui.create()
        ui.show()
        
    except:
        ui.deleteLater()

So … I think your parenting and layout setups are wonky?

My first debugging step was not doing this in maya … and running your code and clicking the expand button made a new window appear somehow. So then I looked, and your group box didn’t have a parent. And neither did any of your layout objects … so you need to fix your parenting issues.

Unfortunately, I don’t have time to dig deeper right now. However, I would suggest trying to use qt designer to build a rough layout, and then convert the .ui to .py to use as a starting point.

2 Likes

Thank you very much.
Aha, yes, I will have to fix that. Isn’t it weird that it works in Maya though?

So I edited my code and yes, I think every UI element I create should reference self so it’s under the window. Correct?
I just thought as long as I parented them to the correct UI element it was fine.

I edited my buttons, but I can’t seem to hide the QGroupBox and resize the layout in one function =/. If I click twice on button number 2 it works…

from maya import OpenMayaUI as omui
from PySide2 import QtGui
from PySide2 import QtWidgets
from PySide2 import __version__
from shiboken2 import wrapInstance 

def maya_main_window():
    '''
    Return the Maya main window widget as a Python object
    '''
    main_window_ptr = omui.MQtUtil.mainWindow()
    return wrapInstance(long(main_window_ptr), QtWidgets.QWidget)
    
    
class TestTool(QtWidgets.QWidget):
    def __init__(self, parent=maya_main_window()):
        super(TestTool, self).__init__(parent)

        
    newNames = {}
    renameDict = {}
    state = 0
    
    def create(self):
        
        self.setWindowTitle("Test")
        self.setWindowFlags(QtCore.Qt.Tool)
        self.resize(300, 100) # re-size the window
        
        
        
        # create layout
        self.mainLayout = QtWidgets.QVBoxLayout(self)

        # widgets
        runButton1 = QtWidgets.QPushButton("Enlarge the window")
        runButton1.clicked.connect(self.growWin)# connect button to rename method
        runButton2 = QtWidgets.QPushButton("Shrink the window")
        runButton2.clicked.connect(self.shrinkWin)# connect button to rename method
        runButton3 = QtWidgets.QPushButton("Expand/Collapse")
        runButton3.clicked.connect(self.showGrpLayout)# connect button to rename method
        
        self.mainLayout.addWidget(runButton1)
        self.mainLayout.addWidget(runButton2)
        self.mainLayout.addWidget(runButton3)

        self.grpBox = QtWidgets.QGroupBox(self)
        self.grpBox.setVisible(0)
        
        #Create the group box layout:
        self.group_box_layout = QtWidgets.QVBoxLayout(self)
        
        #Add widgets to the group box layout like this:
        self.group_box_layout.addWidget(QtWidgets.QCheckBox("Check Box 1"))
        self.group_box_layout.addWidget(QtWidgets.QCheckBox("Check Box 2"))
        self.group_box_layout.addWidget(QtWidgets.QCheckBox("Check Box 3"))
        
        #Assign the group box layout to the group box:
        self.grpBox.setLayout(self.group_box_layout)
        #Assing the group box to the main layout:
        self.mainLayout.addWidget(self.grpBox)      
          
        self.setLayout(self.mainLayout)
        
        self.setStyleSheet(
        "QGroupBox {"
        "border: 1px solid;"
        "border-color: rgba(0, 0, 0, 64);"
        "border-radius: 6px;"
        "background-color: rgb(78, 80, 82);"
        "font-weight: bold;"
        "}"
        "QGroupBox::title {"
        "subcontrol-origin: margin;"
        "left: 6px;"
        "top: 4px;"
        "}"
        "QGroupBox::indicator:checked {"
        "image: url(:/arrowDown.png);"
        "}"
        "QGroupBox::indicator:unchecked {"
        "image: url(:/arrowRight.png);"
        "}"
        )

        # to force the widgets to attach to the top
        spaceItem = QtWidgets.QSpacerItem( 10, 10, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
        self.mainLayout.addSpacerItem(spaceItem)
        
    
    def showGrpLayout(self):
        self.state = int(not (bool(self.state)))
        self.grpBox.setVisible(self.state)
        # not sure how I can fetch the window and modify it
        self.resize(300, 100)

    def growWin(self):
        self.resize(300, 250)
        
    def shrinkWin(self):
        self.grpBox.setVisible(0)
        self.state = 0
        self.resize(300, 100)


if __name__ == "__main__":
    try:
        ui.deleteLater()
    except:
        pass
    
    
    try:
        ui = TestTool()
        ui.create()
        ui.show()
        
    except:
        ui.deleteLater()