Search code examples
pythonqgridlayoutpyside6

maintain grid layout widget as square


This should be easy but I'm just becoming more and more confused looking at suggested answers that don't work. All I want to do is to be able to have a bunch of widgets in a grid layout where resizing the window will result in the widgets inside the grid being resized to fill the space where possible but retain their aspect ratio (square in this case as it's a 2x2 grid of squares).

Here is the code without any of my failed attempts to do this. I originally planned to limit the window itself to being a fixed aspect ratio but couldn't get that to work either. I'll settle for the Frame (or other widget/layout) having this feature. The limiting square size is unimportant as long as it stretches and shrinks along with the window, while retaining the square contents. I'm using PySide6 here, but a solution with PyQt5 would be fine. C++, not so much though, I am literate in it. Is custom handling of resizing required? I can't seem to get heightForWidth working.

import sys
from PySide6.QtWidgets import QApplication, QDialog, QWidget, QFrame, QGridLayout


class MyDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.resize(600, 600)
        self.my_frame = QFrame(self)
        self.my_frame.setObjectName(u"my_frame")
        self.my_frame.setGeometry(50, 50, 250, 250)
        self.my_frame.setBaseSize(100, 100)
        self.gridLayout = QGridLayout(self.my_frame)
        self.gridLayout.setSpacing(0)
        self.gridLayout.setObjectName("gridLayout")

        self.top_left = QWidget(self.my_frame)
        self.top_left.setObjectName("top_left")
        self.top_left.setStyleSheet("background-color: rgb(235, 10, 30)")
        self.gridLayout.addWidget(self.top_left, 0, 0, 1, 1)

        self.top_right = QWidget(self.my_frame)
        self.top_right.setObjectName("top_right")
        self.top_right.setStyleSheet("background-color: rgb(55, 122, 70)")
        self.gridLayout.addWidget(self.top_right, 0, 1, 1, 1)

        self.bottom_left = QWidget(self.my_frame)
        self.bottom_left.setObjectName("bottom_left")
        self.bottom_left.setStyleSheet("background-color: rgb(55, 122, 190)")
        self.gridLayout.addWidget(self.bottom_left, 1, 0, 1, 1)

        self.bottom_right = QWidget(self.my_frame)
        self.bottom_right.setObjectName("bottom_right")
        self.bottom_right.setStyleSheet("background-color: rgb(235, 219, 70)")
        self.gridLayout.addWidget(self.bottom_right, 1, 1, 1, 1)


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

grid


Solution

  • QLayouts (like the QGridLayout) are size handlers and are not visual elements so there is no point in saying they are square. On the other hand, the QGridLayout establishes the geometry of the widgets based mainly on the size of the container.

    So in this case you must make it have a square appearance based on the size of the window, so for this you must override the resizeEvent method and calculate the size of the maximum square inscribed in the window. and maybe you should also center the container.

    class MyDialog(QDialog):
        def __init__(self, parent=None):
            super().__init__(parent)
            self.resize(600, 600)
            qss = """
                #top_left{
                    background-color: rgb(235, 10, 30)
                }
                #top_right{
                    background-color: rgb(55, 122, 70)
                }
                #bottom_left{
                    background-color: rgb(55, 122, 190)
                }
                #bottom_right{
                    background-color: rgb(235, 219, 70)
                }
            """
            self.setStyleSheet(qss)
    
            self.my_frame = QFrame(self, objectName="my_frame")
            self.gridLayout = QGridLayout(self.my_frame, objectName="gridLayout")
            self.gridLayout.setSpacing(0)
    
            self.top_left = QWidget(objectName="top_left")
            self.gridLayout.addWidget(self.top_left, 0, 0, 1, 1)
    
            self.top_right = QWidget(objectName="top_right")
            self.gridLayout.addWidget(self.top_right, 0, 1, 1, 1)
    
            self.bottom_left = QWidget(objectName="bottom_left")
            self.gridLayout.addWidget(self.bottom_left, 1, 0, 1, 1)
    
            self.bottom_right = QWidget(objectName="bottom_right")
            self.gridLayout.addWidget(self.bottom_right, 1, 1, 1, 1)
    
        def resizeEvent(self, event):
            super().resizeEvent(event)
    
            l = min(self.width(), self.height())
            center = self.rect().center()
    
            rect = QRect(0, 0, l, l)
            rect.moveCenter(center)
            self.my_frame.setGeometry(rect)