In PyQt, layouts are used to arrange widgets (things like buttons, line edits, etc.). They provide a flexible and efficient way to manage the positioning and sizing of widgets dynamically, when the window is resized. Layouts eliminate the need for manual placement of widgets, ensuring your UI looks organized across different screen sizes and resolutions.
Boilerplate Code
For us to work with layouts, we will need some base code to setup a window and clearly show what space each widget is taking up. The code for which can be found below:
import sys
from PyQt5.QtWidgets import (QLabel, QApplication, QMainWindow, QWidget,
QVBoxLayout)
class ColoredLabel(QLabel):
def __init__(self, color):
super().__init__()
self.setStyleSheet("background-color: {};".format(color))
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My Layout Window")
main_layout = QVBoxLayout()
widget = QWidget()
widget.setLayout(main_layout)
self.setCentralWidget(widget)
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
if __name__ == '__main__':
main()
Starting off at the top, we have some imports for the things we will need.
Below that, on lines 6-9, we create a new class inheriting from QLabel
. This class is for creating a rectangular widget that has a colored background. This will allow us to easily see what part of the UI each widget takes up when we use them in different layouts.
The user provides the name of the color they want the background to be as a string passed to the __init__()
method, and then on line 9 we use the self.setStyleSheet()
method to set the background color.
Lines 12-15, subclass QMainWindow
in order to allow us to create our own custom main window to add content to. Then it initializes the class by running the super class’ init()
method, and setting the title for the window to "My QLineEdit Window"
.
Line 16 creates a QVBoxLayout. We will be covering this layout first. Lines 18-20 create a widget, adds the layout to that widget and sets it as the central widget for this application.
Lines 22-26 define the main()
function which creates an application initialized with the given command line arguments (sys.argv
), creates a main window from our custom class, shows the window, and executes the application.
Lastly, lines 29 and 30 run the main()
function if this is the main file being run.
If you save the file and run it, it should look like this:
Now let’s get to the layouts!
QVBoxLayout
QVBoxLayout
vertically lays out the items added to it with the first at the top and the last at the bottom.
Below is the example code we will be running, but we will highlight and discuss what is different from the base code.
In this example we create three colored labels vertically stacked, with red at the top, yellow in the middle, and blue at the bottom.
import sys
from PyQt5.QtWidgets import (QLabel, QApplication, QMainWindow, QWidget,
QVBoxLayout)
class ColoredLabel(QLabel):
def __init__(self, color):
super().__init__()
self.setStyleSheet("background-color: {};".format(color))
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My Layout Window")
main_layout = QVBoxLayout()
main_layout.addWidget(ColoredLabel("red"))
main_layout.addWidget(ColoredLabel("yellow"))
main_layout.addWidget(ColoredLabel("blue"))
widget = QWidget()
widget.setLayout(main_layout)
self.setCentralWidget(widget)
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
if __name__ == '__main__':
main()
Starting off, I just wanted to point out we imported QVBoxLayout
:
from PyQt5.QtWidgets import (QLabel, QApplication, QMainWindow, QWidget,
QVBoxLayout)
And on line 16, we create a QVBoxLayout
:
main_layout = QVBoxLayout()
On lines 18-20, we create 3 colored labels, red, yellow, and blue, and add them to our QVBoxLayout
with the addWidget()
method.
main_layout.addWidget(ColoredLabel("red"))
main_layout.addWidget(ColoredLabel("yellow"))
main_layout.addWidget(ColoredLabel("blue"))
And if we run that code we should get:
You can stretch the window up, down, left, or right and it expands the widgets to fill the space.
QHBoxLayout
Similar to QVBoxLayout
, QHBoxLayout
horizontally lays out the items added to it with the first at the left and the last at the right.
Below is the example code we will be running, but we will highlight and discuss what is different from the base code.
In this example we create three colored labels horizontally stacked, with red on the left, yellow in the middle, and blue on the right.
import sys
from PyQt5.QtWidgets import (QLabel, QApplication, QMainWindow, QWidget,
QHBoxLayout)
class ColoredLabel(QLabel):
def __init__(self, color):
super().__init__()
self.setStyleSheet("background-color: {};".format(color))
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My Layout Window")
main_layout = QHBoxLayout()
main_layout.addWidget(ColoredLabel("red"))
main_layout.addWidget(ColoredLabel("yellow"))
main_layout.addWidget(ColoredLabel("blue"))
widget = QWidget()
widget.setLayout(main_layout)
self.setCentralWidget(widget)
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
if __name__ == '__main__':
main()
Starting off, I just wanted to point out this time we imported QHBoxLayout
:
from PyQt5.QtWidgets import (QLabel, QApplication, QMainWindow, QWidget,
QHBoxLayout)
And on line 16, we create a QHBoxLayout
:
main_layout = QHBoxLayout()
Like previously, on lines 18-20, we create 3 colored labels, red, yellow, and blue, and add them to our QHBoxLayout
with the addWidget()
method.
main_layout.addWidget(ColoredLabel("red"))
main_layout.addWidget(ColoredLabel("yellow"))
main_layout.addWidget(ColoredLabel("blue"))
And if we run that code we should get:
You can stretch the window up, down, left, or right and it expands the widgets to fill the space.
Combining QVBoxLayout and QHBoxLayout
It turns out you can use QVBoxLayout
and QHBoxLayouts
within each other.
Below is the example code we will be running, but we will highlight and discuss what is different from the base code.
In this example we create two rows of three colored labels, with red, yellow, and blue on the top, and orange, green, and purple on the bottom.
import sys
from PyQt5.QtWidgets import (QLabel, QApplication, QMainWindow, QWidget,
QVBoxLayout, QHBoxLayout)
class ColoredLabel(QLabel):
def __init__(self, color):
super().__init__()
self.setStyleSheet("background-color: {};".format(color))
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My Layout Window")
main_layout = QVBoxLayout()
row1_layout = QHBoxLayout()
row2_layout = QHBoxLayout()
row1_layout.addWidget(ColoredLabel("red"))
row1_layout.addWidget(ColoredLabel("yellow"))
row1_layout.addWidget(ColoredLabel("blue"))
row2_layout.addWidget(ColoredLabel("orange"))
row2_layout.addWidget(ColoredLabel("green"))
row2_layout.addWidget(ColoredLabel("purple"))
main_layout.addLayout(row1_layout)
main_layout.addLayout(row2_layout)
widget = QWidget()
widget.setLayout(main_layout)
self.setCentralWidget(widget)
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
if __name__ == '__main__':
main()
Starting off, I just wanted to point out that we imported both QVBoxLayout
and QHBoxLayout
:
from PyQt5.QtWidgets import (QLabel, QApplication, QMainWindow, QWidget,
QVBoxLayout, QHBoxLayout)
We accomplish this two row layout by creating a QHBoxLayout
for each row and then adding each of the QHBoxLayouts to a QVBoxLayout as seen below. On lines 16-18 we create the three layouts
main_layout = QVBoxLayout()
row1_layout = QHBoxLayout()
row2_layout = QHBoxLayout()
On lines 20-22 we add the labels to the first row’s QHBoxLayout
and on lines 24-26 add the labels to the second row’s QHBoxLayout
.
row1_layout.addWidget(ColoredLabel("red"))
row1_layout.addWidget(ColoredLabel("yellow"))
row1_layout.addWidget(ColoredLabel("blue"))
row2_layout.addWidget(ColoredLabel("orange"))
row2_layout.addWidget(ColoredLabel("green"))
row2_layout.addWidget(ColoredLabel("purple"))
Then, on lines 28 and 29, we add the each of the QHBoxLayout
s to the main_layout which is a QVBoxLayout
.
main_layout.addLayout(row1_layout)
main_layout.addLayout(row2_layout)
And if we run that code we should get:
You can stretch the window up, down, left, or right and it expands the widgets to fill the space.
But what happens if you don’t have even numbers of widgets in each row?
If we comment out line 21, so we no longer are adding a yellow label to the top QHBoxLayout
, this is what you get:
As you can see, the red and blue widgets expand to fill the the entire top row.
But what if you wanted it to more act like a grid and have an empty space where the yellow was? We can accomplish that with our next layout QGridLayout
.
QGridLayout
QGridLayout allows us to layout widgets in a grid, similar to a table.
Below is the example code we will be running, but we will highlight and discuss what is different from the base code.
In this example we will be recreating what we did when we combined QVBoxLayout
and QHBoxLayout
to create two rows of three colored labels, with red, yellow, and blue on the top, and orange, green, and purple on the bottom.
import sys
from PyQt5.QtWidgets import (QLabel, QApplication, QMainWindow, QWidget,
QGridLayout)
class ColoredLabel(QLabel):
def __init__(self, color):
super().__init__()
self.setStyleSheet("background-color: {};".format(color))
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My Layout Window")
main_layout = QGridLayout()
main_layout.addWidget(ColoredLabel("red"), 0, 0)
main_layout.addWidget(ColoredLabel("yellow"), 0, 1)
main_layout.addWidget(ColoredLabel("blue"), 0, 2)
main_layout.addWidget(ColoredLabel("orange"), 1, 0)
main_layout.addWidget(ColoredLabel("green"), 1, 1)
main_layout.addWidget(ColoredLabel("purple"), 1, 2)
widget = QWidget()
widget.setLayout(main_layout)
self.setCentralWidget(widget)
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
if __name__ == '__main__':
main()
Starting off, I just wanted to point out this time we imported QGridLayout
:
from PyQt5.QtWidgets import (QLabel, QApplication, QMainWindow, QWidget,
QGridLayout)
And on line 16, we create a QHBoxLayout
:
main_layout = QGridLayout()
On lines 18-24, we create our labels and add them to the QGridLayout using the addWidget() method. The difference is, after passing in the widget, we also pass in the row, and column of the grid that we want to add the widget to with row 0 being the top and column 0 being the far left:
main_layout.addWidget(ColoredLabel("red"), 0, 0)
main_layout.addWidget(ColoredLabel("yellow"), 0, 1)
main_layout.addWidget(ColoredLabel("blue"), 0, 2)
main_layout.addWidget(ColoredLabel("orange"), 1, 0)
main_layout.addWidget(ColoredLabel("green"), 1, 1)
main_layout.addWidget(ColoredLabel("purple"), 1, 2)
Just for better visualization, here is a rough approximation of what we expect the grid to look like if we added the row and column numbers on top:
And if we run the code we should get:
As you can see, it matches what we previously got when we combined QVBoxLayout
and QHBoxLayout
.
Now back to the question we asked earlier: What if you wanted it to have an empty space where the yellow was? If we comment out line 19, where we add the yellow label, we get the following:
This is exactly what we asked for!
Spans in QGridLayout
But what if we wanted to do something even more complex? What if we wanted to have the red label span two columns going into the space where yellow was, and the blue label spanning two rows taking over the space where purple is. Something like this:
QGridLayout
can do that!
Below is the example code we will be running, but we will highlight and discuss what is different from the base code.
import sys
from PyQt5.QtWidgets import (QLabel, QApplication, QMainWindow, QWidget,
QGridLayout)
class ColoredLabel(QLabel):
def __init__(self, color):
super().__init__()
self.setStyleSheet("background-color: {};".format(color))
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My Layout Window")
main_layout = QGridLayout()
main_layout.addWidget(ColoredLabel("red"), 0, 0, 1, 2)
main_layout.addWidget(ColoredLabel("blue"), 0, 2, 2, 1)
main_layout.addWidget(ColoredLabel("orange"), 1, 0)
main_layout.addWidget(ColoredLabel("green"), 1, 1)
widget = QWidget()
widget.setLayout(main_layout)
self.setCentralWidget(widget)
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
if __name__ == '__main__':
main()
The only code we have to change from our previous example is when we add the widgets, lines 18-22.
main_layout.addWidget(ColoredLabel("red"), 0, 0, 1, 2)
main_layout.addWidget(ColoredLabel("blue"), 0, 2, 2, 1)
main_layout.addWidget(ColoredLabel("orange"), 1, 0)
main_layout.addWidget(ColoredLabel("green"), 1, 1)
The after the row and column, the addWidget()
method can take the the row span and column span.
For the red label we have the row span as 1
, because we want it to take up one row, and the column span set to 2
, because we want it to take up two columns. Similarly for the blue, we have the row span as 2
, because we want it to take up two rows, and the column span set to 1
, because we want it to take up one column.
Two other things to note: First, we removed the yellow and purple labels because the red and blue labels will be taking their spaces. Second, for the orange and green labels, we don’t have to supply the row span or column span because they are both set to 1
by default.
Just for better visualization, here is a rough approximation of what we expect the grid to look like if we added the row, column, row span, and column span numbers on top:
And if we run the code we get:
And if we wanted to, we can still remove widgets from the layout and it will act like we expect it to. So if we comment out line 22, and run it, we get:
QFormLayout
The last layout we are going to talk about is QFormLayout. It allows you to easily layout widgets to create forms by having a QLabel
on the left and an input widget on the right.
Below is the example code we will be running, but we will highlight and discuss what is different from the base code.
In this example we will create a simple form that asks for your first name, last name, and favorite color.
import sys
from PyQt5.QtWidgets import (QLabel, QApplication, QMainWindow, QWidget,
QFormLayout, QLineEdit)
class ColoredLabel(QLabel):
def __init__(self, color):
super().__init__()
self.setStyleSheet("background-color: {};".format(color))
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My Layout Window")
main_layout = QFormLayout()
main_layout.addRow(QLabel("First Name"), QLineEdit())
main_layout.addRow(QLabel("Last Name"), QLineEdit())
main_layout.addRow(QLabel("Favorite Color"), QLineEdit())
widget = QWidget()
widget.setLayout(main_layout)
self.setCentralWidget(widget)
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
if __name__ == '__main__':
main()
Starting off, I just wanted to point out we imported QFormLayout
, QLabel
, and QLineEdit
. If you haven’t worked with QLabel or QLineEdit before, I have tutorials for QLabel
here, and QLineEdit
here.
from PyQt5.QtWidgets import (QLabel, QApplication, QMainWindow, QWidget,
QFormLayout, QLineEdit)
And on line 16, we create a QFormLayout
:
main_layout = QFormLayout()
With QFormLayout, you generally add a row at a time using the addRow()
method. To it, you pass the label describing what you want the user to input, and then the widget you want them to enter the input in. In our case, we are passing them a QLineEdit
.
main_layout.addRow(QLabel("First Name"), QLineEdit())
main_layout.addRow(QLabel("Last Name"), QLineEdit())
main_layout.addRow(QLabel("Favorite Color"), QLineEdit())
And if we run that code we should get:
You can stretch the window up, down, left, or right and it expands the widgets to fill the space.
Note: As mentioned in the Qt documentation for QFormLayout
, depending on the platform, your QFormLayout
may look different than what is shown above to adhere to each platform’s look and feel guidelines.
While you could use a QGridLayout
to get a somewhat similar effect, one of the benefits of using QFormLayout
s, is they adapt to the look and feel guidelines of the platform (as mentioned in the note above).
And those are the most common layouts used in PyQt!