pythonpyside6qgridlayout

Row stretch issues in PySide6 (Qt for Python)


I'm currently developing a Pyside6 Gui Application for my company and was facing some issues. I am completely new to PySide6 and Qt for Python, but i do have some web dev experience.

The problem is as follows: I want to have a QGridLayout where each row takes up the same amount of space. Without setting a row stretch the row 0 takes up most of the space and the rest is cramped together at the bottom of the page

Have a look at the figure

class ToolSelectionWidget(QWidget):
    def __init__(self):
        super().__init__()
        tool_selection_layout = QGridLayout()

        # Load tool checkbox
        self.load_tool_box = QCheckBox("Load grinding wheel")
        self.load_tool_box.setCheckState(Qt.Checked)
        self.load_tool_box.stateChanged.connect(
            self.load_tool_box_state_changed)

        # Wheel Choice
        self.wheel_choice_combobox = QComboBox()
        self.wheel_choice_combobox.addItems(list_wheel_folders(some_path))

        # Save Checkbox
        self.save_as_template_checkbox = QCheckBox("Save wheel as template")
        self.save_as_template_checkbox.setCheckState(Qt.Unchecked)
        self.save_as_template_checkbox.setEnabled(False)

        # Wheel Parameters
        self.inner_radius = QLineEdit()
        self.inner_radius.setEnabled(False)
        self.outer_radius = QLineEdit()
        self.outer_radius.setEnabled(False)
        self.number_of_grains = QLineEdit()
        self.number_of_grains.setEnabled(False)
        self.random_seed = QLineEdit()
        self.random_seed.setEnabled(False)
        self.template_number = QLineEdit()
        self.template_number.setEnabled(False)
        self.tool_selection_title = QLabel(
            "Grinding wheel selection/ creation")

        # Add Widgets to Layout
        # Row 0
        tool_selection_layout.addWidget(
            self.tool_selection_title, 0, 0)
        # Row 1
        tool_selection_layout.addWidget(self.load_tool_box, 1, 0)
        tool_selection_layout.addWidget(self.wheel_choice_combobox, 1, 1)

        # Row 2
        tool_selection_layout.addWidget(QLabel("Inner Radius:"), 2, 0)
        tool_selection_layout.addWidget(self.inner_radius, 2, 1)
        tool_selection_layout.addWidget(QLabel("Outer Radius:"), 2, 2)
        tool_selection_layout.addWidget(self.outer_radius, 2, 3)
        tool_selection_layout.addWidget(QLabel("Number of Grains:"), 2, 4)
        tool_selection_layout.addWidget(self.number_of_grains, 2, 5)
        tool_selection_layout.addWidget(QLabel("Random Seed:"), 2, 6)
        tool_selection_layout.addWidget(self.random_seed, 2, 7)

        # Row 3
        tool_selection_layout.addWidget(self.save_as_template_checkbox, 3, 0)
        tool_selection_layout.addWidget(QLabel("Template Number:"), 3, 1)
        tool_selection_layout.addWidget(self.template_number, 3, 2)

        # set Layout
        self.setLayout(tool_selection_layout)

I already tried QGridLayout.setRwoStretch() and setting the stretch for every row the same, but it does not work. I also already googled for a solution but didnt find something helpful.


Solution

  • This is caused by the sizePolicy() of the widgets.

    By default, QLabel has a Preferred size policy (both horizontally and vertically):

    The sizeHint() is best, but the widget can be shrunk and still be useful. The widget can be expanded, but there is no advantage to it being larger than sizeHint() (the default QWidget policy).

    Controls such as QComboBox, instead, has a Fixed vertical policy, meaning that its vertical size hint (the "optimal" size for the widget) will be used as a fixed height.

    Note that the Fixed size policy is conceptually the same as setting a fixed size (width, height or both) for the widget.

    When widgets are added to a grid, the layout has to decide how to use all the size policies, and rows containing a combo box have a fixed height, their labels will use that height ("there is no advantage to being larger", or taller in this case). The top row, instead, has just a single label, meaning that it can grow vertically.

    There are two possible solution for this:

    1. set a Fixed (or Maximum) vertical size hint for the label:
        self.tool_selection_title.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
    
    1. leave empty rows above, below and between the widgets, and set their stretch to 1:
            tool_selection_layout.addWidget(
                self.tool_selection_title, 1, 0)
            ...
            tool_selection_layout.addWidget(self.load_tool_box, 3, 0)
            ...
            tool_selection_layout.addWidget(QLabel("Inner Radius:"), 5, 0)
            ...
            tool_selection_layout.addWidget(self.save_as_template_checkbox, 7, 0)
            ...
    
            for row in range(tool_selection_layout.rowCount()):
                if not row & 1:
                    tool_selection_layout.setRowStretch(row, 1)
            tool_selection_layout.setRowStretch(8, 1)
    

    Alternatively, if you want to keep the rows with their unit based indexes, you could use the following helper function and call it after the layout is complete:

    def insertStretches(layout):
        widgetData = []
        for i in range(layout.count()):
            item = layout.itemAt(i)
            if item.widget():
                widgetData.append((item.widget(), ) + layout.getItemPosition(i))
        for widget, r, c, rs, cs in widgetData:
            layout.removeWidget(widget)
            layout.addWidget(widget, r * 2 + 1, c, rs, cs)
        for row in range(0, layout.rowCount() + 1, 2):
            layout.setRowStretch(row, 1)
    
    insertStretches(tool_selection_layout)
    

    Note that the above suggestions are valid for boxed layouts, and in both directions (for widgets that have a Fixed horizontal size policy or a fixed/maximum width), but be also aware that they will only work properly with widgets that have both Preferred and Fixed policies: if any widget has an expanding size policy, it will try to get as much space as possible.

    I suggest you to patiently study the documentation about QSizePolicy and do some tests using Designer, by observing the different behaviors of all widget types along with their size policy property.