Python Graphical User Interface

Getting Insights of Layout Management

A.I Hub
26 min readSep 17, 2024
Image By Author

Mastering widget placement in PyQt5 is the key to building clean, intuitive and highly functional user interfaces. Whether you are crafting a sleek dashboard or a data rich control panel understanding how to position widgets precisely can make or break the user experience. By using absolute positioning, you gain total control over the layout, while layout classes offer flexibility and adaptability for various screen sizes and orientations. The QBox layout simplifies alignment and the horizontal (QHBox) and vertical (QVBox) layouts ensure that widgets flow smoothly. As for the QGrid layout, it offers structured, grid like precision with features like spanning and stretching that give you even more control. Basic grid layouts help you get started quickly, while advanced span and stretch functions allow for dynamic adjustments and more complex designs. Finally, the QForm layout is perfect for organizing input fields and labels in a structured, easy-to-read manner. Dive into these techniques and elevate your PyQt5 applications to professional grade quality.

Table of Content

  • Introduction
  • Widgets placement using absolute positioning
  • Widgets placement using layout classes
  • QBox layout
  • QH box layout
  • QV box layout
  • Q grid layout
  • Basic Qgrid layout
  • Q grid layout span
  • Q grid layout stretch
  • Q form layout

Introduction

Any application can be created by just dragging and dropping the

widgets in our GUI form from the widget box of Qt Designer. It
may look good when we are using multiple widgets on the same

GUI form and creating an application. We can place the widgets
in any location on the GUI form without giving a notion of their
appearance. However, it may look shabby if we do not place it in
a proper location on the GUI form with any arrangement of

widgets. Such an arrangement leads to the concept of layout

management an approach to place the widgets on our GUI form.

All professional GUI application forms creation uses the concept

of layout managers as it is important for the user to create an

application with a nice look. If we want to organize our widgets,

the preferred approach is to manage the layout with layout

managers. Therefore, whenever there is a requirement to
arrange widgets on a GUI form, we will use the methods present
inside the class of layout manager. The space between parent
and child widgets can be properly utilized in a GUI form and is
useful for communicating between them.

Let us start learning the different ways for layout management in

PyQt5. We will be creating the same application user credentials

app appearance only but now with the usage of the concept of

layouts.

Widgets Placement Using Absolute Positioning

Without using a layout manager, the first approach of creating

layouts in a GUI form that we will learn is, using the absolute

positioning. We can place the widgets in any position in a GUI

form by specifying explicitly the size or position values for each

widget in pixels. It is mainly used in the applications where there

is a requirement to size values and set positions of the widgets
which have to be accommodated within other widgets. Widgets
can be positioned in a GUI form using the absolute positioning
approach, with the help of move(x,y) method.
Here, x and y will be the coordinates of the respective widget
which starts from the upper top left corner (0,0).

Figure 1.1 - Coordinate system of GUI form

From Figure 1.1, we can see that the x position will increase from

left to right and the y position from top to bottom.
Now, we shall create the same GUI form, similar to
run_user_credential_app.py using absolute positioning.

import sys
from PyQt5.QtWidgets import QWidget, QApplication,
QLabel, QLineEdit, QPushButton # RAP1
from PyQt5 import QtGui

class Absolute_Position(QWidget): # RAP2
def __init__(self):
super().__init__()
self.display_widgets() # RAP3
self.setGeometry(0, 0, 398, 229) # RAP4
self.setWindowTitle('User Credentials App') #
RAP5
self.show()

def display_widgets(self):
mylbl1 = QLabel('Enter your username:', self)
# RAP6
myfont = QtGui.QFont() # RAP7
myfont.setFamily("Calibri")
myfont.setPointSize(10)
myfont.setBold(True)
myfont.setWeight(75)
mylbl1.setFont(myfont)
mylbl1.move(14, 50) # RAP8

mylbl2 = QLabel('Enter your password:', self)
# RAP9
myfont = QtGui.QFont() # RAP10
myfont.setFamily("Calibri")
myfont.setPointSize(10)
myfont.setBold(True)
myfont.setWeight(75)
mylbl2.setFont(myfont)
mylbl2.move(14, 120) # RAP11

mylineedit1 = QLineEdit(self) # RAP12
mylineedit1.move(210, 50) # RAP13

mylineedit2 = QLineEdit(self) # RAP14
mylineedit2.move(210, 120) # RAP15

mybtn = QPushButton(self) # RAP16
mybtn.setText('Confirm') # RAP17
mybtn.move(140, 180) # RAP18
if __name__ == '__main__':
myapp = QApplication(sys.argv)
mywindow = Absolute_Position()
sys.exit(myapp.exec_())

Output:

Figure 1.2 - Output of section 2_Example1.py

On comparing with the original user credentials app, refer to

Figure 1.2, we can see that there is hardly any difference
between the original code and the one we wrote using absolute
positioning. Now, let us understand the code.

  • In RAP1, first import the sys module for accessing the

    command line arguments. Then we will be importing from
    the class which creates desktop style UIs of PyQt5 package. QtWidgets, for creating an empty GUI and an
    application handler, that is, QApplication. In addition to it,
    we are also importing other widgets like QLabel, QLineEdit
    and QPushButton.
  • In RAP2, we are creating a class named Absolute_Position

    inheriting from the base class QWidget.
  • In RAP3, we are calling the display_widgets method present
    inside the class QWidget.
  • In RAP4, we are displaying the GUI form of 398 (width) by

    229 (height) pixels at position (0,0) on the screen. It can be

    displayed anywhere on the screen by changing the xpos

    and ypos which is at present set at (0,0).
  • In RAP5, we are setting the title of our GUI form, that is,

    window as User Credentials App.
  • In RAP6, we are creating a QLabel object mylbl1 for using in
    our GUI form and passing the text as Enter your username and
    self as parameters, whose default display will be at the top
    left position.
  • In RAP7, we are creating an instance of QFont class and

    setting the QLabel object mylbl1 font family as Calibri, font

    size as 10 and font-weight as Bold. For boldness control to

    be fine, we have set weight as 75.
  • In RAP8, the QLabel object mylbl1 is placed at position 14

    pixels towards the right and 50 pixels below from (0,0).
  • In RAP12, we are creating a QLineEdit object mylineedit1

    whose default display will be at the top left position.
  • In RAP13, the QLineEdit object mylineedit1 is placed at

    position 210 pixels towards the right and 50 pixels below

    from (0,0).
  • In RAP14, we are creating a QLineEdit object mylineedit2

    whose default display will be at the top left position.
  • In RAP15, the QLineEdit object mylineedit2 is placed at

    position 210 pixels towards the right and 120 pixels below

    from (0,0).
  • In RAP16, we are creating an instance of QPushButton object

    mybtn whose default display will be at the top left position and passing a parameter self indicates that the pushbutton

    is part of the GUI form.
  • In RAP17, the text for the QPushButton object mybtn is set as
    confirm and will get displayed on the GUI form.
  • In RAP18, the QPushButton object mybtn is placed at position 140
    pixels towards the right and 180 pixels below from (0,0).

Absolute positioning is quite simple but there are some limitations, such as the follows:

  • The widget’s size and position will not be changing even if

    the GUI form will be resized.
Figure 1.3 -Intact position of widgets on resizing

We can see from Figure 1.3, that even on resizing the GUI
form, the widget’s size and position are still the same.

  • If we are creating any application using absolute

    positioning, the layout may be different on various
    platforms.
  • In some cases, there may be requirements of redesigning

    the form. In such cases, layout modification will be tedious

    and time consuming.

The reader should understand the concepts of absolute
positioning, before knowing about widgets placement using
layout classes.

Widget Placement Using Layout Classes

API of PyQt5 has a more elegant way of widgets positioning by

providing layout classes which we will learn in subsequent

sections.

QBox Layout

Suppose in our application, there is a requirement to organize or

arrange widgets either horizontally or vertically. In such cases,
we will use QBoxLayout class. The two basic layout management
classes for arranging the widgets either horizontally or vertically
are QHBoxLayout and QVBoxLayout. First, we will see the code for
arranging the widgets in a row, side-by-side using QHBoxLayout
with the usage of methods viz addStretch, addWidget, addLayout.

QHBox Layout

In this section, we will see the code to arrange the widgets in a

row, side-by-side using QHBoxLayout without addstretch.

import sys
from PyQt5.QtWidgets import QWidget, QApplication,
QLabel, QHBoxLayout, QDesktopWidget # QHBEG1
from PyQt5 import QtGui

class HBox_without_stretch(QWidget):
def __init__(self):
super().__init__()
self.display_widgets()
self.setGeometry(0, 0, 398, 229)
self.setWindowTitle('QHBoxLayout withoutstretch')# QHBEG2

self.movecenter()
self.show()

def display_widgets(self):
mylbl1 = QLabel('Label1:', self)
myfont = QtGui.QFont()
myfont.setFamily("Calibri")
myfont.setPointSize(10)
myfont.setBold(True)
myfont.setWeight(75)
mylbl1.setFont(myfont)
mylbl2 = QLabel('Label2', self)
myfont = QtGui.QFont()
myfont.setFamily("Calibri")
myfont.setPointSize(10)
myfont.setBold(True)
myfont.setWeight(75)
mylbl2.setFont(myfont)
# QHBoxlayout
myhbox = QHBoxLayout()# QHBEG7
myhbox.addWidget(mylbl1)# QHBEG8
myhbox.addWidget(mylbl2)# QHBEG9
self.setLayout(myhbox)# QHBEG10
def movecenter(self):
myfrm_gmtry = self.frameGeometry()# QHBEG3
mycenter =
QDesktopWidget().availableGeometry().center()# QHBEG4
myfrm_gmtry.moveCenter(mycenter)# QHBEG5
self.move(myfrm_gmtry.topLeft())# QHBEG6
if __name__ == '__main__':
myapp = QApplication(sys.argv)
mywindow = HBox_without_stretch()
sys.exit(myapp.exec_())

Output:

Figure 1.4 - Output of section 2_Example2.py

The preceding example depicts the example of QHBoxLayout with

usage of addWidget and setLayout methods. Here, we have not used

addStretch() method. In the above example, we created two Label

widgets and arranged them horizontally. Moreover, a method is being called to place the GUI form in the center of the screen.

Let us learn these new concepts.

  • In QHBEG1, first import the sys module for accessing the
    command line arguments. Then we will be importing from

    the class which creates desktop style UIs of PyQt5
    package, that is, QtWidgets, for creating an empty GUI and
    an application handler, that is, QApplication. In addition to it,
    we are also importing other widgets such as QLabel,
    QHBoxLayout and QDesktopWidget.
  • In QHBEG2, we are setting the title of our GUI form, that is,

    window, as QHBoxLayout without stretch.
  • From QHBEG3 to QHBEG6, we are displaying the GUI form in the
    center of the screen.
  • In QHBEG3, we are trying to get the location and size
    information of the GUI form using frameGeometry method that
    are stored in the myfrm_gmtry object.
  • In QHBEG4, the monitor’s screen center position is getting
    determined.
  • In QHBEG5, the rectangular position of the GUI form is moved
    to the screen center.
  • In QHBEG6, the current GUI form will move to the rectangle

    position (myfrm_gmtry) which is moved to the screen center

    resulting in the matching of the current GUI form center

    with the screen center, such that the GUI form will appear

    in the center. Just try to comment this line only. You will find that the
    GUI form will be placed at the top left of monitor screen.
  • In QHBEG7, an instance of QHBoxLayout myhbox is created.
  • In QHBEG8, label object mylbl1 will be added to the box layout

    object myhbox.
  • In QHBEG9, label object mylbl2 will be added to the box layout

    object myhbox.
  • In QHBEG10, we are setting the main layout of the GUI form.

    In other words, we are setting the horizontal layout to the

    GUI form.

We can see that the label widgets are centered horizontally and

vertically in the GUI frame.

Moreover, in the above example, we have not used the
addStretch() method, at all. So, the whole width of the QHBoxLayout
will be averaged by the widgets. For example, if the QHBoxLayout

width is ‘a’ and the number of widgets are ‘x’, then each widget

width will be ‘a/x’.
One important thing to observe is that on resizing the GUI form
the widgets size will also be re-sized.

Figure 1.5 - Resizing of GUI form

Now, we will look at another example of QHBoxLayout, but this time
with addStretch method.

QHBoxLayout.addStretch(size)

Here, size can be an integer number say -1,0 or 1. So, an empty stretchable box can be created using addStretch and
is a nice way for filling up the blank space.

We will only be adding the addStretch() method to QHBoxLayout
instance object, changing the class name to HBox_with_stretch and

changing the GUI form title as shown below from the previous

code example.

self.setWindowTitle('QHBoxLayout with addstretch')
# QHBoxlayout
myhbox = QHBoxLayout()
myhbox.addStretch() # added
myhbox.addWidget(mylbl1)
myhbox.addWidget(mylbl2)
self.setLayout(myhbox)

Output:

Figure 1.6 - QHBoxLayout with addStretch

Refer to Figure 1.6 where we are indicating a double-sided arrow

for the display of an empty box when compared with Figure 1.7.

Output:

Figure 1.7 - QHBoxLayout with addStretch displaying empty box

We can conclude the following points from above:

  • The width of these widgets, that is, Label widget’s width

    will be the original width and will not be the average whole

    width of QHBoxLayout.
  • An empty stretchable box will be added in QHBoxLayout and

    stretched as long as it fills up the whole QHBoxLayout.

So, from the preceding figures, we understand that the width of
the Label widgets is original and an empty box fills the remaining

space.
Now, we shall addStretch before and after these two label widgets
and see what output we shall see.

# QHBoxlayout
myhbox = QHBoxLayout()
myhbox.addStretch() # added
myhbox.addWidget(mylbl1)
myhbox.addWidget(mylbl2)
self.setLayout(myhbox)
myhbox.addStretch() # added

Output:

Figure 1.8 - QHBoxLayout with addStretch at 2 places

From Figure 1.9, we can conclude that the width of the two label

widgets are original and the remaining space is averaged by two
empty boxes as depicted in the following figure.

Output:

Figure 1.9 - QHBoxLayout with empty boxes at 2 places

Now, we shall see the difference between addStretch(1) and

addStretch(2).

import sys
from PyQt5.QtWidgets import QWidget, QApplication,
QLabel, QHBoxLayout, QDesktopWidget
from PyQt5 import QtGui

class HBox_stretch1_2(QWidget):
def __init__(self):
super().__init__()
self.display_widgets()
self.setGeometry(0, 0, 398, 229)
self.setWindowTitle('QHBoxLayout stretch1_2')
self.movecenter()
self.show()

def display_widgets(self):
mylbl1 = QLabel('Label1:', self)
myfont = QtGui.QFont()
myfont.setFamily("Calibri")
myfont.setPointSize(10)
myfont.setBold(True)
myfont.setWeight(75)
mylbl1.setFont(myfont)
mylbl2 = QLabel('Label2', self)
myfont = QtGui.QFont()
myfont.setFamily("Calibri")
myfont.setPointSize(10)
myfont.setBold(True)
myfont.setWeight(75)
mylbl2.setFont(myfont)
# QHBoxlayout
myhbox = QHBoxLayout()
myhbox.addStretch(1) # addstretch_1
myhbox.addWidget(mylbl1)
myhbox.addStretch(2) # addstretch_2
myhbox.addWidget(mylbl2)
self.setLayout(myhbox)

def movecenter(self):
myfrm_gmtry = self.frameGeometry()
mycenter =
QDesktopWidget().availableGeometry().center()
myfrm_gmtry.moveCenter(mycenter)
self.move(myfrm_gmtry.topLeft())
if __name__ == '__main__':
myapp = QApplication(sys.argv)
mywindow = HBox_stretch1_2()
sys.exit(myapp.exec_())

Output:

Figure 1.10 - Output of section 2_Example5.py

a) Output displaying the difference between addstretch(1) and addstretch(2) of
Section 2_Example5.py.

b) Output displaying with stretch factor ‘x’ between Label1: and Label2 of
Section 2_Example5.py.

  • myhbox.addStretch(1): An empty stretchable box will grow
    horizontally from the layout’s left side moving rightwards.
    In the right image of Figure 1.10, it is denoted as x.
  • myhbox.addStretch(2): An empty stretchable box will be grown
    horizontally twice from the layout’s left side moving

    rightwards. In the right image of Figure 1.10, it is denoted

    as 2*x.

Now, observe the output for the following piece of code:

# QHBoxlayout
myhbox = QHBoxLayout()
myhbox.addStretch() # addstretch_0
myhbox.addWidget(mylbl1)
myhbox.addStretch(2) # addstretch_2
myhbox.addWidget(mylbl2)
self.setLayout(myhbox)

Output:

Figure 1.11 - QHBoxLayout with addStretch() and addStretch(2)

We can clearly see from the output that when the first stretch is

0, that is, addStretch() and then addstretch(2), then the second

empty box would not grow horizontally at all.

Now, we shall change the code as shown below:

# QHBoxlayout
myhbox = QHBoxLayout()
myhbox.addStretch(2) # addstretch_2
myhbox.addWidget(mylbl1)
myhbox.addStretch(1) # addstretch_1
myhbox.addWidget(mylbl2)
self.setLayout(myhbox)

Output:

Figure 1.12 - QHBoxLayout with addStretch(2) and addStretch(1)

The first empty stretchable box will grow horizontally twice (2*x)

from the layout’s left side moving rightwards and then ‘x’ times.

QVBox Layout

Now, let us learn how to arrange the same two label widgets

vertically using QVBoxLayout.

import sys
from PyQt5.QtWidgets import QWidget, QApplication,
QLabel, QVBoxLayout, QDesktopWidget # QVBEG1
from PyQt5 import QtGui
class VBox_with_stretch(QWidget):
def __init__(self):
super().__init__()
self.display_widgets()
self.setGeometry(0, 0, 398, 229)
self.setWindowTitle('QVBoxLayout with
addstretch') # QVBEG2
self.movecenter()
self.show()

def display_widgets(self):
mylbl1 = QLabel('Label1:', self)
myfont = QtGui.QFont()
myfont.setFamily("Calibri")
myfont.setPointSize(10)
myfont.setBold(True)
myfont.setWeight(75)
mylbl1.setFont(myfont)
mylbl2 = QLabel('Label2', self)
myfont = QtGui.QFont()
myfont.setFamily("Calibri")
myfont.setPointSize(10)
myfont.setBold(True)
myfont.setWeight(75)
mylbl2.setFont(myfont)
# QHBoxlayout
myvbox = QVBoxLayout() # QVBEG3
myvbox.addStretch() # QVBEG4
myvbox.addWidget(mylbl1) # QVBEG5
myvbox.addWidget(mylbl2) # QVBEG6
self.setLayout(myvbox) # QVBEG7
myvbox.addStretch() # QVBEG8

def movecenter(self):
myfrm_gmtry = self.frameGeometry()
mycenter =
QDesktopWidget().availableGeometry().center()
myfrm_gmtry.moveCenter(mycenter)
self.move(myfrm_gmtry.topLeft())
if __name__ == '__main__':
myapp = QApplication(sys.argv)
mywindow = VBox_with_stretch()
sys.exit(myapp.exec_())

Output:

Figure 1.13 - Output of section 2_Example8.py
  • In QVBEG1, first import the sys module for accessing the

    command line arguments. Then we will be importing from

    the class which creates desktop-style UIs of PyQt5 package. QtWidgets, for creating an empty GUI and an application
    handler, that is, QApplication. In addition to it, we are also
    importing other widgets such as QLabel, QVBoxLayout and
    QDesktopWidget.
  • In QVBEG2, we are setting the title of our GUI form, that is,

    window as QVBoxLayout with addstretch.
  • In QVBEG3, an instance of QVBoxLayout myvbox is created.
  • In QVBEG4, we are adding the addStretch() method to QVBoxLayout

    instance object myvbox.
  • In QVBEG5, label object mylbl1 will be added to the box layout

    object myvbox.
  • In QVBEG7, we are setting the main layout of the GUI form. In

    other words, we are setting the vertical layout to the GUI

    form.
  • In QVBEG8, we are again adding the addStretch() method to

    QVBoxLayout instance object myvbox.
Figure 1.14 - QVBoxLayout with 2 empty boxes

From Figure 1.14, we can conclude that the width of the two
label widgets is original and the remaining space is averaged by

two empty boxes highlighted in the figure of QVBoxLayout.

After learning the concepts of QHBoxLayout and QVBoxLayout, let us
make an application similar to run_user_credential_app.py.

import sys
from PyQt5.QtWidgets import QWidget, QApplication,
QLabel, QHBoxLayout, QVBoxLayout, QDesktopWidget,
QLineEdit, QPushButton # QBX1
from PyQt5 import QtGui
class BoxLayout_user_credential_App(QWidget):
def __init__(self):
super().__init__()
self.display_widgets()
self.setGeometry(0, 0, 398, 229)
self.setWindowTitle('BoxLayout User Credential App') # QBX2
self.movecenter()
self.show()

def display_widgets(self):
mylbl1 = QLabel('Enter your username:', self)
myfont = QtGui.QFont()
myfont.setFamily("Calibri")
myfont.setPointSize(10)
myfont.setBold(True)
myfont.setWeight(75)
mylbl1.setFont(myfont)
mylbl2 = QLabel('Enter your password:', self)
myfont = QtGui.QFont()
myfont.setFamily("Calibri")
myfont.setPointSize(10)
myfont.setBold(True)
myfont.setWeight(75)
mylbl2.setFont(myfont)

mylineedit1 = QLineEdit(self)
mylineedit2 = QLineEdit(self)
mybtn = QPushButton(self)
mybtn.setText('Confirm')
myhbox1 = QHBoxLayout() # QBX3
myhbox1.setSpacing(60) # QBX4
myhbox1.addStretch() # QBX5
myhbox1.addWidget(mylbl1) # QBX6
myhbox1.addWidget(mylineedit1) # QBX7
myhbox1.addStretch() # QBX8

# QBX9
myhbox2 = QHBoxLayout()
myhbox2.setSpacing(60)
myhbox2.addStretch()
myhbox2.addWidget(mylbl2)
myhbox2.addWidget(mylineedit2)
myhbox2.addStretch()

# QBX10
myhbox3 = QHBoxLayout()
myhbox3.addStretch()
myhbox3.addWidget(mybtn)
myhbox3.addStretch()
# QHBoxlayout
myvbox = QVBoxLayout() # QBX11
myvbox.addStretch() # QBX12
myvbox.addLayout(myhbox1) # QBX13
myvbox.addStretch() # QBX14
myvbox.addLayout(myhbox2) # QBX15
myvbox.addStretch() # QBX16
myvbox.addLayout(myhbox3) # QBX17
self.setLayout(myvbox) # QBX18

def movecenter(self):
myfrm_gmtry = self.frameGeometry()
mycenter =
QDesktopWidget().availableGeometry().center()
myfrm_gmtry.moveCenter(mycenter)
self.move(myfrm_gmtry.topLeft())
if __name__ == '__main__':
myapp = QApplication(sys.argv)
mywindow = BoxLayout_user_credential_App()
sys.exit(myapp.exec_())

Output:

Figure 1.15 - Output of section 2_Example9.py
  • In QBX1, first import the sys module for accessing the

    command line arguments. Then we will be importing from

    the class which creates desktop-style UIs of PyQt5

    package, that is, QtWidgets , for creating an empty GUI and

    an application handler, that is, QApplication . In addition to it,

    we are also importing other widgets like QLabel, QHBoxLayout,QVBoxLayout,QDesktopWidget, QLineEdit and QPushButton.
  • In QBX2, we are setting the title of our GUI form, that is,

    window as BoxLayout User Credential App.
  • In QBX3, an instance of QHBoxLayout is being created with an
    object name myhbox1.
  • In QBX4, the spacing between the items when arranging

    horizontally is 60. It will take 60 as an argument. We are
    using the setSpacing method with the QHBoxlayout object.
  • In QBX5, QBX8, QBX12, QBX14 and QBX16, an empty stretchable box
    is created using addStretch for filling up the blank space.
  • In QBX6, label object mylbl1 will be added to the box layout

    object myhbox1.
  • In QBX7, lineEdit object mylineedit1 will be added to the box
    layout object myhbox1.
  • In QBX9, an instance of QHBoxLayout is created with the object
    name myhbox2. The spacing between the items when
    arranged horizontally is 60. An empty stretchable box is

    created using addStretch for filling up the blank space. Label
    object mylbl2 will be added to the box layout object myhbox2.
    LineEdit object mylineedit2 will be added to the box layout
    object myhbox2.
  • In QBX10, an instance of QHBoxLayout is created with an object

    name myhbox3. An empty stretchable box is created using

    addStretch for filling up the blank space. Push button object

    mylbl2 will be added to the box layout object myhbox2.
  • In QBX11, an instance of QVBoxLayout is being created with the
    object name myvbox. It will be acting as a container for

    arranging the layouts QHBoxLayout vertically from top to
    bottom.
  • In QBX13, we are adding a layout myhbox1 to the QVBoxLayout
    object myvbox.
  • In QBX15, we are adding a layout myhbox2 to the QVBoxLayout
    object myvbox.
  • In QBX17, we are adding a layout myhbox3 to the QVBoxLayout
    object myvbox.
  • In QBX18, we are setting the main layout of the GUI form. In

    other words, we are setting the vertical layout to the GUI

    form. This is all about layout management using QBoxLayout.

QGrid Layout

Suppose in our application, there is a requirement to arrange

widgets in a grid of rows and columns. In that case, we must

know about QGridLayout. It is one of the most universal layout

classes for arranging the widgets in pairs, where each widget will

have a relative position, and will be arranged in a grid of rows

and columns. Its application ranges from adding buttons while

creating a calculator app or making a virtual keypad and so on.
The pair of coordinates for arranging the widgets and placing them on the cell should be zero based integer numbers.

QGridLayout will leave the cell empty if no widgets are added to a

given cell. We will use overloaded implementations of the
addWidget method if we are adding widgets to a grid layout.

  • addWidget(self,QWidget, int row, int column): This method will
    add a widget at the defined row and column.
  • addWidget(self,QWidget, int row, int column, int rowSpan, int
    columnSpan, alignment):
    This method will add a widget at
    defined row and column spanning multiple rows or columns
    or both. Alignment is an optional argument whose default
    value is 0, meaning that the entire cell will be filled by the
    widget.

Let us see some of the examples using QGridLayout.

Basic QGrid Layout

import sys
from PyQt5.QtWidgets import QWidget, QApplication,
QLabel, QGridLayout # GLEG1_1
from PyQt5 import QtGui
class GridLayout_Eg1(QWidget):
def __init__(self):
super().__init__()
self.display_widgets()
self.setGeometry(0, 0, 398, 229)
self.setWindowTitle('Basic GridLayout
example') #GLEG1_2
self.show()
def display_widgets(self):
# writing the grid portion
mygrid_layout = QGridLayout()# GLEG1_3
self.setLayout(mygrid_layout)# GLEG1_4

for outer in range(4):
for lower in range(3):
mylbl = QLabel('Label' + str(outer) +
str(lower),self)# GLEG1_5
myfont = QtGui.QFont()
myfont.setFamily("Calibri")
myfont.setPointSize(10)
myfont.setBold(True)
myfont.setWeight(75)
mylbl.setFont(myfont)
mygrid_layout.addWidget(mylbl, outer,
lower)# GLEG1_6
if __name__ == '__main__':
myapp = QApplication(sys.argv)
mywindow = GridLayout_Eg1()
sys.exit(myapp.exec_())

Output:

Figure 1.16 - Output of section 2_Example10.py
  • In GLEG1_1, first import the sys module for accessing the

    command line arguments. Then we will be importing from

    the class which creates desktop style UIs of PyQt5
    package, that is, QtWidgets, for creating an empty GUI and
    an application handler, that is, QApplication. In addition to it,
    we are also importing other widgets such as QLabel and
    QGridLayout.
  • In GLEG1_2, we are setting the title of our GUI form, that is,

    window, as Basic GridLayout example.
  • In GLEG1_3, an instance of QGridLayout is created with the
    object name as mygrid_layout.
  • In GLEG1_4, we are setting the main layout of the GUI form.

    In other words, we are setting the Grid layout to the GUI

    form.
  • In GLEG1_5, an instance of QLabel object mylbl is created, and
    setting QLabel object font family is Calibri, font size is 10,

    and font-weight is Bold. For boldness control to be fine, we

    have set weight as 75.
  • In GLEG1_6, we are adding a QLabel widget at the defined row
    and column. The first row will be 0 and then the widget will
    be added to columns 0, 1, and 2. That is why we have appended the Label text with rows and columns for better

    identification to the reader.
  • GLEG1_5 and GLEG1_6 will be repeated 11 times more than the
    initial count. Now, we will see another example of spanning
    in GridLayout.

QGrid Layout Span

In QGridLayout span, the default value of rowspan and columnspan

is 1. If a positive value is mentioned, then the cell widget will
extend to that value. If it is -1, then the extension will be done

either to the right or bottom edge.

import sys
from PyQt5.QtWidgets import QWidget, QApplication,
QLabel, QGridLayout, QPushButton # GLEG2_1
from PyQt5 import QtGui
class GridLayout_Eg2(QWidget):
def __init__(self):
super().__init__()
self.display_widgets()
self.setGeometry(0, 0, 398, 229)
self.setWindowTitle('GridLayout with span')#
GLEG2_2
self.show()

def display_widgets(self):
# writing the grid portion
mygrid_layout = QGridLayout()
self.setLayout(mygrid_layout)

mybtn1 = QPushButton("Row Spanning merging 2
rows")
mygrid_layout.addWidget(mybtn1, 0,0, 2,1)#
GLEG2_3

mybtn2 = QPushButton("Column Spanning")
mygrid_layout.addWidget(mybtn2, 2,0, 1,3)#
GLEG2_4

for outer in range(3,5):
for lower in range(3,5):
mylbl = QLabel('Label' + str(outer) +
str(lower),self)# GLEG2_5
myfont = QtGui.QFont()
myfont.setFamily("Calibri")
myfont.setPointSize(10)
myfont.setBold(True)
myfont.setWeight(75)
mylbl.setFont(myfont)
mygrid_layout.addWidget(mylbl, outer,
lower)# GLEG2_6
if __name__ == '__main__':
myapp = QApplication(sys.argv)
mywindow = GridLayout_Eg2()
sys.exit(myapp.exec_())

Output:

Figure 1.17 - Output of section 2_Example11.py
  • In GLEG2_1, first import the sys module for accessing the

    command line arguments. Then, we will be importing from

    the class which creates desktop-style UIs of PyQt5

    package, that is, QtWidgets, for creating an empty GUI and

    an application handler, that is, QApplication . In addition to it,

    we are also importing other widgets such as QLabel,

    QGridLayout, QPushButton.
  • In GLEG2_2, we are setting the title of our GUI form, that is,

    window as GridLayout with span.
  • In GLEG2_3, the QPushButton object mybtn1 will be added at a
    defined row = 0 and column as 0 with rowSpan value as 2 and

    columnSpan value as 1.
  • In GLEG2_4, the QPushButton object mybtn2 will be added at a
    defined row = 2 and column as 0 with rowSpan value as 1 and

    columnSpan value as 3.
  • In GLEG2_5, an instance of QLabel object mylbl is created and
    setting QLabel object font family as Calibri, font size as 10
    font weight as Bold. For boldness control to be fine, we

    have set weight as 75.
  • In GLEG2_6, we are adding QLabel widget at the defined row
    and column. The first row will be 3 and then the widget will
    be added to columns 3 and 4. That is why we have appended the Label text with rows and columns for better

    identification to the reader.

QGrid Layout Stretch

Before writing the code, we will see methods, setColumnStretch and
setRowStretch. These two methods generally focus on the stretch
factor of column/row. More available space will be taken, based
on the higher value of the stretch factor.

import sys
from PyQt5.QtWidgets import QWidget, QApplication,
QTextEdit, QGridLayout # GLEG3_1
from PyQt5 import QtGui
class GridLayout_Eg3(QWidget):
def __init__(self):
super().__init__()
self.display_widgets()
self.setGeometry(0, 0, 398, 229)
self.setWindowTitle('GridLayout with
stretch')# GLEG3_2
self.show()
def display_widgets(self):
# writing the grid portion
mygrid_layout = QGridLayout()
self.setLayout(mygrid_layout)

for outer in range(1,4):
for lower in range(1,4):
mytextedit = QTextEdit(self)# GLEG3_3

mytextedit.setPlaceholderText(str(outer) +
str(lower))# GLEG3_4
mygrid_layout.addWidget(mytextedit,
outer, lower)# GLEG3_5

mygrid_layout.setColumnStretch(outer,outer+1)# GLEG3_6
if __name__ == '__main__':
myapp = QApplication(sys.argv)
mywindow = GridLayout_Eg3()
sys.exit(myapp.exec_())

Output:

Figure 1.18 - Output of section 2_Example12.py
  • In GLEG3_1 , first import the sys module for accessing the

    command line arguments. Then we will be importing from

    the class which creates desktop-style UIs of PyQt5

    package, that is, QtWidgets , for creating an empty GUI and

    an application handler, that is, QApplication. In addition to it,

    we are also importing other widgets such as QTextEdit and

    QGridLayout.
  • In GLEG3_2, we are setting the title of our GUI form, that is,

    window, as GridLayout with stretch.
  • In GLEG3_3, an instance of QTextEdit is created with the object
    name as mytextedit.
  • In GLEG3_4, the texteditor placeholder text will be a

    concatenation of outer and lower and will be displayed as

    grayed out text.
  • In GLEG3_5, we are adding QTextEdit widget at a defined row
    and column. The first row will be 1 and then the widget will
    be added to columns 1,2 and 3.
  • In GLEG3_6, the setColumnStretch factor will take the first
    argument as outer and 2nd as outer+1. So, value will be
    (1,2). Therefore, column width order will be more for 3rd
    column, then 2nd and finally the 1st one as shown in the
    output. The rows stretch factor will be set to 1, 2 and 3.

Now, let us learn how to develop the same User Credential App

using QGridLayout.

import sys
from PyQt5.QtWidgets import QWidget, QApplication,
QLabel, QGridLayout, QPushButton, QLineEdit ,
QHBoxLayout
from PyQt5 import QtGui
class GridLayout_User_Credential_App(QWidget):
def __init__(self):
super().__init__()
self.display_widgets()
self.setGeometry(0, 0, 398, 229)
self.setWindowTitle('GridLayout with User
Credential App')
self.show()

def display_widgets(self):
# writing the grid portion
mygrid_layout = QGridLayout()
self.setLayout(mygrid_layout)
mylbl1 = QLabel('Enter your username:',self)
mylbl1.setMinimumWidth(161)
myfont = QtGui.QFont()
myfont.setFamily("Calibri")
myfont.setPointSize(10)
myfont.setBold(True)
myfont.setWeight(75)
mylbl1.setFont(myfont)
mygrid_layout.addWidget(mylbl1, 1, 0, 1, 2)

mylbl2 = QLabel('Enter your password:',self)
mylbl2.setMinimumWidth(161)
myfont = QtGui.QFont()
myfont.setFamily("Calibri")
myfont.setPointSize(10)
myfont.setBold(True)
myfont.setWeight(75)
mylbl2.setFont(myfont)
mygrid_layout.addWidget(mylbl2, 3, 0, 1, 2)

mylineEdit1 = QLineEdit(self)
mylineEdit1.setMinimumWidth(161)
mygrid_layout.addWidget(mylineEdit1, 1, 2,1,2)
mygrid_layout.setColumnStretch(1,2)
mylineEdit2 = QLineEdit(self)
mylineEdit2.setMinimumWidth(161)
mygrid_layout.addWidget(mylineEdit2, 3, 2,1,2)

myhbox = QHBoxLayout()
myhbox.addStretch()
mybtn1 = QPushButton("Confirm")
myhbox.addWidget(mybtn1)
myhbox.addStretch()

mygrid_layout.addLayout(myhbox,4,1,1,3)
if __name__ == '__main__':
myapp = QApplication(sys.argv)
mywindow = GridLayout_User_Credential_App()
sys.exit(myapp.exec_())

Output:

Figure 1.19 - Output of section 2_Example13.py

QForm Layout

Whenever there is a requirement to create an application in 2-

column form where each row consists of a label (first column)

associated with an entry field (second column), then we can use

QFormLayout. An important overloaded method which is most

commonly used in QFormLayout, will be the addRow() method, which
is described as follows.

  • addRow(QLabel, QWidget): This method will add a row

    containing the label and its widget in the second column.
  • addRow(QWidget): This method will add a row containing the
    widget spanning 2 columns and stretching it to a GUI form.
  • addRow(QLayout): This method will add a specified layout

    spanning both columns, or we can say that it will be used

    for nesting the layouts.
  • addRow(QLabel, QLayout): This method will add a row

    containing the label and its child layout in the second

    column.

Let us create the same user credential app using QFormLayout this
time.

import sys
from PyQt5.QtWidgets import QWidget, QApplication,
QLabel, QFormLayout, QPushButton,QHBoxLayout,
QLineEdit , QHBoxLayout # QFL_1
from PyQt5 import QtGui
class FormLayout_User_Credential_App(QWidget):
def __init__(self):
super().__init__()
self.display_widgets()
self.setGeometry(0, 0, 398, 229)
self.setWindowTitle('FormLayout with User
Credential App')# QFL_2
self.show()

def display_widgets(self):
# writing the grid portion
myfrm_layout = QFormLayout()

mylbl1 = QLabel('Enter your username:',self)
mylbl1.setMinimumWidth(161)
myfont = QtGui.QFont()
myfont.setFamily("Calibri")
myfont.setPointSize(10)
myfont.setBold(True)
myfont.setWeight(75)
mylbl1.setFont(myfont)
mylbl2 = QLabel('Enter your password:',self)
mylbl2.setMinimumWidth(161)
myfont = QtGui.QFont()
myfont.setFamily("Calibri")
myfont.setPointSize(10)
myfont.setBold(True)
myfont.setWeight(75)
mylbl2.setFont(myfont)
mylineEdit1 = QLineEdit(self)
mylineEdit1.setMinimumWidth(161)
mylineEdit2 = QLineEdit(self)
mylineEdit2.setMinimumWidth(161)

myhbox = QHBoxLayout()
myhbox.addStretch()
mybtn1 = QPushButton("Confirm")
myhbox.addWidget(mybtn1)
myhbox.addStretch()
myfrm_layout.addRow(mylbl1, mylineEdit1)#
QFL_3
myfrm_layout.addRow(mylbl2, mylineEdit2)#
QFL_4
myfrm_layout.addRow(myhbox)# QFL_5
self.setLayout(myfrm_layout)# QFL_6
if __name__ == '__main__':
myapp = QApplication(sys.argv)
mywindow = FormLayout_User_Credential_App()
sys.exit(myapp.exec_())

Output:

Figure 1.20 - Output of section 2_Example14.py
  • In QFL_1, first import the sys module for accessing the

    command line arguments. Then we will be importing from

    the class which creates desktop style UIs of PyQt5
    package, that is, QtWidgets, for creating an empty GUI and
    an application handler, that is, QApplication. In addition to it, we are also importing other widgets such as QLabel,
    QFormLayout, QPushButton, QLineEdit.
  • In QFL_2, we are setting the title of our GUI form, that is,

    window, as Form Layout with User Credential App.
  • In QFL_3, we are adding a row containing label object mylbl1

    in the first column and QLineEdit object mylineEdit1 in 2nd
    column respectively.
  • In QFL_4, we are adding a row containing label object mylbl2

    in the first column and QLineEdit object mylineEdit2 in 2nd
    column respectively.
  • In QFL_5, we are adding a row containing QHBoxLayout object.
  • In QFL_6, we are setting the main layout of GUI form. In

    other words, we are setting the QFormLayout to the GUI form.

Now, from QFormLayout code, we can see that the space
between the 2 QLabel or 2 QLineEdit widgets is very less. The
same happens between QLabel or QLineEdit widgets too. So, in such
cases, we need to provide spacing. We will be providing vertical
spacing between the widgets using the SetVerticalSpacing method

and horizontal spacing using the setHorizontalSpacing method.

We will modify the previous code by introducing the concept of

Vertical and Horizontal spacer.

myhbox1 = QHBoxLayout() # addition1
myhbox1.addStretch() # addition2
myfrm_layout.addRow(myhbox1)
myfrm_layout.addRow(mylbl1, mylineEdit1)
myfrm_layout.setHorizontalSpacing(50) #
addition3
myfrm_layout.setVerticalSpacing(50) #
addition4
myfrm_layout.addRow(mylbl2, mylineEdit2)
myfrm_layout.setVerticalSpacing(50) #
addition5
myfrm_layout.addRow(myhbox1)

Output:

Figure 1.21 - O/P of vertical and horizontal spacing approach and original user
credentials app
  • In addition1, an instance of QHBoxLayout object myhbox1 is
    created.
  • In addition2, an empty stretchable box is created for the
    myhbox1 object using addStretch for filling up the blank space.
  • In addition3, the space between the widgets lying side by

    side (horizontally only) will be 50. setHorizontalSpacing takes

    an integer value as an argument.
  • In addition4 and addition5, the space between the widgets

    lying out vertically (only) will be 50. setVerticalSpacing takes

    an integer value as an argument.

Now, the most important observation of all these concepts about
widgets placement which we saw till now, is that we can easily

manage using Qt Designer.

Figure 1.22 - Qt Designer View of Layout, Spacers in Widget Box and toolbar controls

Let us show their usage with the help of figures, by playing with

default GUI form. This is inarguably one of the best ways to learn.

Figure 1.23 - Qt Designer toolbar with Default GUI Form

Now, we will be selecting in pairs say Btn1 and Btn2 as one and

Btn3 and Btn4 as another. As displayed in Figure 1.24 with arrow
sign which is Layout Horizontally in Qt Designer, we will be first
selecting the first pair and then click the button with arrow sign.
The same methodology will be repeated for second pair too. Just
observe the rectangle portion which are treated as pairs. The
result after performing these activities are displayed in the
figure below.

Figure 1.24 - Qt Designer toolbar with Layout Horizontally

Now, we will be selecting in pairs say Btn1 and Btn3 as one and

Btn2 and Btn4 as another. As displayed in Figure 1.25 with arrow
sign which is Layout Vertically in Qt Designer, we will be first
selecting the first pair and then click the button with arrow sign.
The same methodology will be repeated for second pair too. Just
observe the rectangle portion which are treated as pairs. The

result after performing these activities are displayed in the

figure below.

Figure 1.25 - Qt Designer toolbar with Layout Vertically

Just observe the outputs when QPushButton widget, in pairs, are

being selected and corresponding Toolbar icons are being
pressed highlighted with arrow sign against each feature as
shown in Figure 1.26.

Figure 1.26 - Qt Designer toolbar with lay out horizontally in Splitterfrom default GUI
form

In Figure 1.27, the QPushButton widgets are selected in pairs and

stretched horizontally by clicking Lay Out Horizontally in splitter

icon.

Figure 1.27 - Qt Designer toolbar with Lay Out Vertically in Splitterfrom Default GUI
form
Figure 1.28 - Qt designer toolbar with layout in a grid transformation from default
GUI form
Figure 1.29 - On selection of Label1 and Btn2 widget as a pair and then clicking
Layout in a Grid button as displayed with arrow sign
Figure 1.30 - On selection of Label2 and Btn4 widget as a pair and then clicking
Layout in a Grid button as displayed with arrow sign
Figure 1.31 - On selecting both pairs and then clicking Layout in a Grid button as
displayed with arrow sign
Figure 1.31 - Final layout form as displayed using grid transformation in Qt Designer

From Figure 1.28 to Figure 1.32 we are transforming a default
GUI form by arranging the widgets in a Grid layout using Qt Designer toolbar Lay Out in a Grid. Just observe the steps that we

have described in the preceding figures, for explaining how the

widgets are used in pairs and then laying out these widgets in a

grid of rows and columns. The figure with arrow marks are self-

explanatory.

Let us now go over Figure 1.33 containing the default GUI form.

Figure 1.33 - Qt Designer toolbar with layout in a form layout transformation from
default GUI form
Figure 1.34 - On selection of Label1 and Line Edit1 widget as a pair and then clicking
Lay Out in a Form Layout button as displayed with arrow sign
Figure 1.35 - On selection of Label2 and Line Edit2 widget as a pair and then clicking
Lay Out in a Form Layout button as displayed with arrow sign
Figure 1.36 - On selecting both pairs and then clicking Layout in a Form Layout
button as displayed with arrow sign
1.37 - Final layout form as displayed using form layout transformation in Qt
Designer

From Figure 1.33 to Figure 1.37, we are transforming a default
GUI form by arranging the widgets in a Form layout using Qt
Designer toolbar Lay Out in a Form Layout. Just observe the steps
that we have described in the preceding figure, for explaining
how the widgets are used in pairs and then laying out these
widgets in a 2-column form. The figure with arrow marks are self explanatory.
Now, we will see how to use vertical and horizontal spacer in Qt
Designer. Refer to Figure 1.38 containing the default GUI form
with horizontal spacer usage.

Figure 1.38 - Qt Designer form displaying horizontal spacer usage from Default GUI
form
Figure 1.39 - Qt Designer form inserting horizontal spacer as shown using arrow on
first pair of Label1 and Line Edit1 widget after performing Lay Out in a Grid

transformation
Figure 1.40 - Qt Designer form inserting horizontal spacer as shown using arrow on
second pair of Label1 and Line Edit1 widget after performing Lay Out in a Grid

transformation
Figure 1.41 - Final layout form as displayed using horizontal spacer usage by
pressing Ctrl + R

From Figure 1.38 to Figure 1.41, we can see the usage of a
horizontal spacer from the widget box on a default GUI form. By

default, the widgets pair QLabel and QLineEdit are arranged in a

grid of rows and columns. Then, a horizontal spacer is introduced

between the widgets thus indicating the usage of a horizontal
spacer. Finally, by pressing Ctrl + R, we can view the final output
of the GUI form.

Refer to Figure 1.42 containing the default GUI form with vertical

spacer usage.

Figure 1.42 - Qt Designer form displaying vertical spacer usage from Default GUI
Form
Figure 1.43 - Qt Designer form inserting vertical spacer as shown using arrow on
first pair of Label1 and Label2 widget after performing Lay Out in a Grid

transformation
Figure 1.44 - Qt Designer after stretching Label1 and Label2 pair as they are
arranged in a vertical layout
Figure 1.45 - Qt Designer with Line Edit1 Line Edit2 widget stretched vertically after
introducing vertical spacer between them as shown using arrow
Figure 1.46 - Final layout form as displayed using vertical spacer usage in Qt
Designer by pressing Ctrl + R

From Figure 1.42 to Figure 1.46, the widgets pair of QLabel and

QLineEdit are arranged by introducing a vertical spacer between
them and are stretched to the heights mentioned. Finally, by

pressing Ctrl + R, we can see the final output of the GUI form.

Conclusion

In this section, we will learned how to arrange the widgets using

absolute positioning and layout classes. We saw different

approaches of creating User Credential App application using

absolute positioning, QBoxLayout, QGridLayout and QFormLayout class. This section is important to understand, since in forthcoming

sections, we will be displaying multiple applications using all

these concepts. We will write the code on how to arrange the

widgets by applying drag and drop of widgets from Widget Box

and using toolbar icons to arrange the layouts for creating our

application.

--

--

A.I Hub
A.I Hub

Written by A.I Hub

We writes about Data Science | Software Development | Machine Learning | Artificial Intelligence | Ethical Hacking and much more. Unleash your potential with us

No responses yet