maintain grid layout widget as square
Asked Answered
X

1

2

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

Xerosis answered 19/2, 2021 at 18:7 Comment(5)
PyQt5 is not the same as PySide6, firstly PyQt5 is a binding of Qt5 and PySide6 is of Qt6, and Qt5 with Qt6 are different (Qt6 has many bugs and default behavior changes compared to Qt5). Also both bindings use a different tool to be built: sip and shiboken. So please don't use both tags.Yukoyukon
@Yukoyukon Please stop removing references to PyQt from the question. There's no point in restricting potential answers by limiting it to either PySide or PyQt. I'm happy with an answer in either. I could have just as easily posed the question using PyQt5 imports instead.Xerosis
I very much appreciate your answer to my question but I do not understand your attitude here. You say "in many cases the solution may work for both libraries but not in others" - and that would mean different solutions would help different members of the community and therefore someone working in one environment might choose to switch to overcome problems with the other. More information is better than less.Xerosis
mmm, I think we're looking at things differently. For me the tags are used to keep things in order so if I use the X technology then I should use the X tag otherwise we would use more general tags like "programming", "algorithm", etc. And the tags go in the question so it is relevant that it makes a match. If, on the other hand, it is very generic, it makes the search difficult and we end up with thousands of posts where the OPs ask the same thing and the community answers the same.Yukoyukon
You are looking at it from the point of view of the questioner and many times it does not look for duplicates but I am seeing it from the point of view of the person who answers. I do not want to publish the same answer from another public and I prefer to mark it as a duplicate since this way we save the community time in obtaining the answer in addition to a simpler search.Yukoyukon
Y
5

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)
Yukoyukon answered 19/2, 2021 at 20:37 Comment(3)
Thank you. That does the trick. Though need the line 'from PySide6.QtCore import QRect'. Is there any reason this would not also work with PyQt5?Xerosis
@EugeneGill Please read my other comment, the problem is not that the solution does not work for PyQt but that it is for reasons of order. If the posts are not classified then the same question and answers tend to be published, and the objective of SO is to have a collection of quality Q&A where any user can find an answer, and if the information is messy the search is more complicated by doing that again post the same question making the community strive more unnecessarilyYukoyukon
In trying to find a solution without overriding resize, I came across heightForWidth as part solution to similar questions. This seems to have been something of a red-herring.Xerosis

© 2022 - 2024 — McMap. All rights reserved.