Why do QGridLayout Widgets move around when adding new ones?

Question:

I can’t seem to wrap my head around how they work. The the best for placing multiple widgets seems to be QGridLayout but when I add something into a specific row/column and later decide to add somthing into another row/column everything shifts and it’s just really frustrating.

For example I would not even be able to do such a simple layout as the google mainpage. When I add a searchbar to the place I want it to be and then add an image/text above it everything moves into weird spots etc and I can’t find proper explanations online on how to handle it. Thus I would be delighted if anyone could explain it to an absolute beginner, like me, in an understandable way.

So when I have the following code:

from PyQt6.QtWidgets import *
import sys
import numpy as np
import os
from PyQt6 import QtCore, QtGui
from PyQt6.QtCore import QEvent, Qt
from PyQt6.QtGui import QPalette, QColor
from pathlib import Path




app = QApplication(sys.argv)
    

class MainWindow(QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.resize(1024, 768)
        self.setWindowTitle("Tracker")
        

        layout = QGridLayout()
        self.setLayout(layout)
        #layout.setRowMinimumHeight(0, 50)

        #layout.setColumnMinimumWidth(0,50)


        self.input = QLineEdit(self)
        self.input.setPlaceholderText('Enter Username')
        layout.addWidget(self.input,1,1)
        self.input.setFixedSize(300,30)



        self.darkmode_check = QCheckBox('Darkmode',self)
        self.darkmode_check.toggled.connect(self.darkmode)
        self.darkmode_check.setChecked(True)
        self.darkmode_check.move(0,0)
        
    

    def darkmode(self):
            
            if self.darkmode_check.isChecked() == True:
                app.setStyleSheet(Path('D:CODELeague Codedarkorange.qss').read_text())
            else:
                app.setStyleSheet(Path('D:CODELeague Codeclassic_edit.qss').read_text())   
 
    
       

window = MainWindow()
window.show()
sys.exit(app.exec())

I get this screen which is what I want.

this screen

When I only want to add a text above this search bar:

by adding

self.text = QLabel(self)
self.text.setText('Tracker')
layout.addWidget(self.text,0,1)

I get this:

this:

which is all over the place.

Does anyone have good explanations on GridLayout or can recommend good websites for it? I found a lot about what the grid looks like etc but nothing helped (and also some posts giving me 3×3 grids, some 4×4 etc, I’m just confused at this point)
I basically just want to place a searchbar in the middle, then a text above that and keep on adding little things here and there.

Thank you

Asked By: Tabi

||

Answers:

Qt basic layouts always try to evenly divide the available space in its "cells", and each widget will have that space reserved (even if it doesn’t use all of it).

Note that different widget types have also different size policies that tell the layout how it should allocate the available space and eventually set the geometry of those widgets.

For instance, QLineEdit has a Fixed vertical policy, meaning that its size hint will always be considered as the only valid height (which is similar to calling setFixedHeight() or setFixedSize() as you did).

QLabel, instead, has a Preferred size policy, meaning that if there’s space left in the layout, it can take advantage of it.

When you only have the line edit, the layout only has that widget, so it will place it in the center (because you didn’t specify an alignment). But when you add the label, the layout finds that the line edit needs a very small space, so it will leave the remaining to the label, hence your result.

For a simple case like this, you can just specify a proper alignment when adding the widgets: when the alignment is provided, the item will not try to occupy the whole cell and the layout will align it to the available space of that layout cell.

        layout.addWidget(self.text, 0, 0, alignment=Qt.AlignBottom)
        layout.addWidget(self.input, 1, 0, alignment=Qt.AlignTop)

Note that I changed the column to 0, as there is no point in placing widgets in the second column if there’s nothing in the first, unless you want to get advantage of setColumnStretch() or setColumnMinimumWidth().

Also consider that for this kind of "wide" layouts with lots of empty spaces it’s usually better to use nested layouts, or use container widgets.

For instance:

        layout = QGridLayout(self)
        centerLayout = QVBoxLayout()
        layout.addLayout(centerLayout, 0, 0, alignment=Qt.AlignCenter)

        # ...
        centerLayout.addWidget(self.text)
        centerLayout.addWidget(self.input)

Or, alternatively:

        layout = QGridLayout(self)
        centerWidget = QWidget()
        layout.addWidget(centerWidget, 0, 0, alignment=Qt.AlignCenter)

        centerLayout = QVBoxLayout(centerWidget)
        # ... as above

Try to remove the alignment argument in the two examples above and you’ll see the difference.

I suggest you to do some experiments using layouts in Qt Designer, which makes it easier to immediately understand how layout work and behave with different widget types.

Answered By: musicamante