2Obe
2Obe

Reputation: 3710

PyQt QGridLayout() misbehaviour

I try to get a GUI application to work but unfortunately the spacing of the single canvas objects is not as expected. I want to have the following layout:

enter image description here

For the Grid Layout I use the following code:

# Adapt Widget StyleSheet
self.button_classification.setStyleSheet("font: bold 10pt Consolas; color: #009682")  # #009682 is the KIT-Green
self.button_start.setStyleSheet('font: bold 10pt Consolas; color: #009682; border-style: outset; border-width: 2px; border-radius: 10px; border-color: #646873; padding: 6px')
self.layout4_image_widget.setStyleSheet('background-image: url(Python_logo.png);')

# Define Layouts
self.window = QWidget()
self.layout = QGridLayout()

# Add Widgets to layouts
self.layout.addWidget(self.canvas_left, 0, 0, 5, 5)
self.layout.addWidget(self.canvas_right, 0, 5, 5, 5)
self.layout.addWidget(self.canvas_right_buttom, 8, 7, 3, 3)
self.layout.addWidget(self.canvas_right_top, 5, 7, 3, 3)
self.layout.addWidget(self.canvas_middle_buttom, 8, 3, 3, 4)
self.layout.addWidget(self.canvas_middle_top, 5, 3, 3, 4)
self.layout.addWidget(self.layout4_image_widget, 5, 0, 3, 3)
self.layout.addWidget(self.button_start, 8, 0, 1, 3)
self.layout.addWidget(self.button_classification, 9, 0, 1, 3)
self.layout.addWidget(self.LCD_Number, 10, 0, 1, 3)
self.window.setLayout(self.layout)

But unfortunately what I receive as result is:

enter image description here

So the rows in the middle 5-7 are to wide.

Upvotes: 0

Views: 1223

Answers (2)

musicamante
musicamante

Reputation: 48231

Using column and row spans for widgets doesn't work as you seem to believe. Each "cell" in a grid layout might have different sizes, and it all depends on each widget sizeHint and sizePolicy.

All widget have a default size policy, which is a property that a widget uses to tell its container how it behaves when it is resized (should it expand, have a fixed height/width, can it shrink, etc).
By default, buttons and checkboxes have a fixed vertical policy, which means that they can never grow or shrink.

Every layout has a stretch value that can be assigned for each of its "slots". The stretch always defaults to 0, which leaves to each widget the possibility to grow or shrink to fit its needs, but when that value is set the available spaces are computed by the sum of each stretch in the layout and then divided equally. For example, if you have a simple layout with 2 widgets and a stretch=1 for the first and 2 for the second, the available space will be divided by 3 (1 + 2), with the first occupying a third of it and the second 2 thirds.
For grid layouts you can then use setRowStretch() and setColumnStretch() to achieve something like you proposed, but this would work only for widget that have compatible policies (preferred or expanding), and since in your case those buttons have fixed height it wouldn't be enough.

There are two possibilities, but in both cases I'll use a simpler layout, since there's no need to use a high count of rows and columns for the reasons explained above and setRowStretch/setColumnStretch can be used instead.

The layout uses just 4 columns:

+----+---+---+----+
|        |        |
+----+---+---+----+
|    |       |    |
+----+---+---+----+
|    |       |    |
+----+---+---+----+

The first row has two "cells" with a 2 column span, and the following rows have the 2 central slots united as a single cell with a similar span.

1. Manually set the policy for those widgets

In this case there are actually 5 rows, the first has a rowStretch=5, the second 3, and the remaining 1. By manually setting the vertical size policy to the widgets, they can grow to the height required by the stretch of the row they are occupying.

    # first row, notice the spans
    self.layout.addWidget(self.canvas_left, 0, 0, 1, 2)
    self.layout.addWidget(self.canvas_right, 0, 2, 1, 2)

    # second row, if the span is not specified defaults to (1, 1)
    self.layout.addWidget(self.layout4_image_widget, 1, 0)
    self.layout.addWidget(self.canvas_middle_top, 1, 1, 1, 2)
    self.layout.addWidget(self.canvas_right_top, 1, 3)

    # left widgets for third, fourth and fifth rows
    self.layout.addWidget(self.button_start, 2, 0)
    self.layout.addWidget(self.button_classification, 3, 0)
    self.layout.addWidget(self.LCD_Number, 4, 0)

    # remaining widgets, notice the vertical and horizontal spans
    self.layout.addWidget(self.canvas_right_buttom, 2, 1, 3, 2)
    self.layout.addWidget(self.canvas_middle_buttom, 2, 3, 3, 1)

    # the column stretches set based on the grid given by you
    self.layout.setColumnStretch(0, 3)
    self.layout.setColumnStretch(1, 2)
    self.layout.setColumnStretch(2, 2)
    self.layout.setColumnStretch(3, 3)

    # the row stretches, with the final 3 rows set to 1
    self.layout.setRowStretch(0, 5)
    self.layout.setRowStretch(1, 3)
    self.layout.setRowStretch(2, 1)
    self.layout.setRowStretch(3, 1)
    self.layout.setRowStretch(4, 1)

    # the default policy for both buttons and checkboxes is QSizePolicy.Minimum
    # horizontally, and QSizePolicy.Fixed vertically, let's just change the latter
    self.button_start.setSizePolicy(
        QSizePolicy.Minimum, QSizePolicy.Preferred)
    self.button_classification.setSizePolicy(
        QSizePolicy.Minimum, QSizePolicy.Preferred)

Unfortunately this isn't perfect: as you can see the button might become too big and the checkbox has a lot of unnecessary margins that could be used by the LCD widget instead.

size policy result

2. Use a container widget

In this case there only 3 rows, and an extra QWidget is used as container for the widgets on the bottom left.

    self.layout.addWidget(self.canvas_left, 0, 0, 1, 2)
    self.layout.addWidget(self.canvas_right, 0, 2, 1, 2)

    self.layout.addWidget(self.layout4_image_widget, 1, 0)
    self.layout.addWidget(self.canvas_middle_top, 1, 1, 1, 2)
    self.layout.addWidget(self.canvas_right_top, 1, 3)

    # create a container widget and add it to the main layout
    self.container = QWidget()
    self.layout.addWidget(self.container, 2, 0)
    containerLayout = QVBoxLayout(self.container)

    # add the widgets to the container
    containerLayout.addWidget(self.button_start)
    containerLayout.addWidget(self.button_classification)
    containerLayout.addWidget(self.LCD_Number)

    # the remaining widgets on the third row
    self.layout.addWidget(self.canvas_right_buttom, 2, 1, 1, 2)
    self.layout.addWidget(self.canvas_middle_buttom, 2, 3)

    self.layout.setColumnStretch(0, 3)
    self.layout.setColumnStretch(1, 2)
    self.layout.setColumnStretch(2, 2)
    self.layout.setColumnStretch(3, 3)

    # there are only 3 rows, the second and third have the same row span
    self.layout.setRowStretch(0, 5)
    self.layout.setRowStretch(1, 3)
    self.layout.setRowStretch(2, 3)

In this case the space usage is more optimized, leaving more space to those widgets that need it (the LCD).

container result

Upvotes: 2

thesylio
thesylio

Reputation: 144

https://doc.qt.io/qtforpython/PySide2/QtWidgets/QGridLayout.html#PySide2.QtWidgets.PySide2.QtWidgets.QGridLayout.setRowStretch

maybe there is something to do with this function... as they say the stretch factor has something to do with the row's relative space occupation and its default value is 0. You could maybe try setting them at 1 for each row.

Upvotes: 0

Related Questions