New to Qt5 and Python? Check out our complete PyQt5 tutorial.

Read the free tutorial below or
Unlock video course

Layout management

So far we've successfully created a window, and we've added a widget to it. However we normally want to add more than one widget to a window, and have some control over where it ends up. To do this in Qt we use layouts. There are 4 basic layouts available in Qt, which are listed in the following table.

Layout Behaviour
QHBoxLayout Linear horizontal layout
QVBoxLayout Linear vertical layout
QGridLayout In indexable grid XxY
QStackedLayout Stacked (z) in front of one another

You can actually design and lay out your interface graphically using the Qt designer, which we will cover later. Here we're using code, as it's simpler to understand and experiment with the underlying system.

As you can see, there are three positional layouts available in Qt. The VBoxLayout, QHBoxLayout and QGridLayout. In addition there is also QStackedLayout which allows you to place widgets one on top of the other within the same space, yet showing only one layout at a time.

Load up a fresh copy of `MyApp_window.py` and save it under a new name for this section.

Before we start experimenting with the different layouts, we're first going to create a very simple custom widget that we can use to visualise the layouts that we use. Add the following code to your file as a new class at the top level:

python
class Color(QWidget):

    def __init__(self, color, *args, **kwargs):
        super(Color, self).__init__(*args, **kwargs)
        self.setAutoFillBackground(True)
        
        palette = self.palette()
        palette.setColor(QPalette.Window, QColor(color))
        self.setPalette(palette)

In this code we subclass QWidget to create our own custom widget Color. We accept a single parameter when creating the widget — color (a str). We first set .setAutoFillBackground to True to tell the widget to automatically fill its background with the window cooler. Next we get the current palette (which is the global desktop palette by default) and change the current QPalette.Window color to a new QColor described by the value color we passed in. Finally we apply this palette back to the widget. The end result is a widget that is filled with a solid color, that we specified when we created it.

If you find the above confusing, don't worry too much. We'll cover custom widgets in more detail later. For now it's sufficient that you understand that calling you can create a solid-filled red widget by doing the following:

Color('red')

First let's test our new Color widget by using it to fill the entire window in a single color. Once it’s complete we can add it to the QMainWindow using .setCentralWidget and we get a solid red window.

python
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        
        self.setWindowTitle("My Awesome App")

        widget = Color('red')
        self.setCentralWidget(widget)

Run it! The window will appear, filled completely with the color red. Notice how the widget expands to fill all the available space.

Next we'll look at each of the available Qt layouts in turn. Note that to add our layouts to the window we will need a dummy QWidget to hold the layout.

QVBoxLayout vertically arranged widgets

With QVBoxLayout you arrange widgets one above the other linearly. Adding a widget adds it to the bottom of the column.

A QVBoxLayout, filled from top to bottom.
A QVBoxLayout, filled from top to bottom.

Let’s add our widget to a layout. Note that in order to add a layout to the QMainWindow we need to apply it to a dummy QWidget. This allows us to then use .setCentralWidget to apply the widget (and the layout) to the window. Our coloured widgets will arrange themselves in the layout, contained within the QWidget in the window. First we just add the red widget as before.

python
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        
        self.setWindowTitle("My Awesome App")

        layout = QVBoxLayout()

        layout.addWidget(Color('red'))
        
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

Run it! Notice the border now visible around the red widget. This is the layout spacing — we'll see how to adjust that later.

If you add a few more coloured widgets to the layout you’ll notice that they line themselves up vertical in the order they are added.

python
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        
        self.setWindowTitle("My Awesome App")

        layout = QVBoxLayout()

        layout.addWidget(Color('red'))
        layout.addWidget(Color('green'))
        layout.addWidget(Color('blue'))
        
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

QHBoxLayout horizontally arranged widgets

QHBoxLayout is the same, except moving horizontally. Adding a widget adds it to the right hand side.

A QHBoxLayout, filled from left to right.
A QHBoxLayout, filled from left to right.

To use it we can simply change the QVBoxLayout to a QHBoxLayout. The boxes now flow left to right.

python
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        
        self.setWindowTitle("My Awesome App")

        layout = QHBoxLayout()

        layout.addWidget(Color('red'))
        layout.addWidget(Color('green'))
        layout.addWidget(Color('blue'))
        
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

Nesting layouts

For more complex layouts you can nest layouts inside one another using .addLayout on a layout. Below we add a QVBoxLayout into the main QHBoxLayout. If we add some widgets to the QVBoxLayout, they’ll be arranged vertically in the first slot of the parent layout.

python
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        
        self.setWindowTitle("My Awesome App")

        layout1 = QHBoxLayout()
        layout2 = QVBoxLayout()
        layout3 = QVBoxLayout()

        layout2.addWidget(Color('red'))
        layout2.addWidget(Color('yellow'))
        layout2.addWidget(Color('purple'))

        layout1.addLayout( layout2 )

        layout1.addWidget(Color('green'))

        layout3.addWidget(Color('red'))
        layout3.addWidget(Color('purple'))
        
        layout1.addLayout( layout3 )
        
        widget = QWidget()
        widget.setLayout(layout1)
        self.setCentralWidget(widget)

Run it! The widgets should arrange themselves in 3 columns horizontally, with the first column also containing 3 widgets stacked vertically. Experiment!

You can set the spacing around the layout using .setContentMargins or set the spacing between elements using .setSpacing.

layout1.setContentsMargins(0,0,0,0)
layout1.setSpacing(20)

The following code shows the combination of nested widgets and layout margins and spacing. Experiment with the numbers til you get a feel for them.

python
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        
        self.setWindowTitle("My Awesome App")

        layout1 = QHBoxLayout()
        layout2 = QVBoxLayout()
        layout3 = QVBoxLayout()

        layout1.setContentsMargins(0,0,0,0)
        layout1.setSpacing(20)

        layout2.addWidget(Color('red'))
        layout2.addWidget(Color('yellow'))
        layout2.addWidget(Color('purple'))

        layout1.addLayout( layout2 )

        layout1.addWidget(Color('green'))

        layout3.addWidget(Color('red'))
        layout3.addWidget(Color('purple'))
        
        layout1.addLayout( layout3 )
        
        widget = QWidget()
        widget.setLayout(layout1)
        self.setCentralWidget(widget)

QGridLayout widgets arranged in a grid

As useful as they are, if you try and using QVBoxLayout and QHBoxLayout for laying out multiple elements, e.g. for a form, you’ll find it very difficult to ensure differently sized widgets line up. The solution to this is QGridLayout.

A QGridLayout showing the grid positions for each location.
A QGridLayout showing the grid positions for each location.

QGridLayout allows you to position items specifically in a grid. You specify row and column positions for each widget. You can skip elements, and they will be left empty.

Usefully, for QGridLayout you don't need to fill all the positions in the grid.

A QGridLayout with unfilled slots.
A QGridLayout with unfilled slots.
python
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        
        self.setWindowTitle("My Awesome App")

        layout = QGridLayout()

        layout.addWidget(Color('red'), 0, 0)
        layout.addWidget(Color('green'), 1, 0)
        layout.addWidget(Color('blue'), 1, 1)
        layout.addWidget(Color('purple'), 2, 1)

        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

QStackedLayout multiple widgets in the same space

The final layout we’ll cover is the QStackedLayout. As described, this layout allows you to position elements directly in front of one another. You can then select which widget you want to show. You could use this for drawing layers in a graphics application, or for imitating a tab-like interface. Note there is also QStackedWidget which is a container widget that works in exactly the same way. This is useful if you want to add a stack directly to a QMainWindow with .setCentralWidget.

QStackedLayout — in use only the uppermost widget is visible, which is by default the first widget added to the layout.
QStackedLayout — in use only the uppermost widget is visible, which is by default the first widget added to the layout.
QStackedLayout, with the 2nd (1) widget selected and brought to the front.
QStackedLayout, with the 2nd (1) widget selected and brought to the front.
python
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        
        self.setWindowTitle("My Awesome App")

        layout = QStackedLayout()

        layout.addWidget(Color('red'))
        layout.addWidget(Color('green'))
        layout.addWidget(Color('blue'))
        layout.addWidget(Color('yellow'))
        
        layout.setCurrentIndex(3)

        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

QStackedWidget is exactly how tabbed views in applications work. Only one view ('tab') is visible at any one time. You can control which widget to show at any time by using .setCurrentIndex() or .setCurrentWidget() to set the item by either the index (in order the widgets were added) or by the widget itself.

Below is a short demo using QStackedLayout in combination with QButton to to provide a tab-like interface to an application:

python
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        
        self.setWindowTitle("My Awesome App")

        pagelayout = QVBoxLayout()
        button_layout = QHBoxLayout()
        layout = QStackedLayout()

        pagelayout.addLayout(button_layout)
        pagelayout.addLayout(layout)

        for n, color in enumerate(['red','green','blue','yellow']):
            btn = QPushButton( str(color) )
            btn.pressed.connect( lambda n=n: layout.setCurrentIndex(n) )
            button_layout.addWidget(btn)
            layout.addWidget(Color(color))

        widget = QWidget()
        widget.setLayout(pagelayout)
        self.setCentralWidget(widget)

Helpfully. Qt actually provide a built-in TabWidget that provides this kind of layout out of the box - albeit in widget form. Below the tab demo is recreated using QTabWidget:

python
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        
        self.setWindowTitle("My Awesome App")


        tabs = QTabWidget()
        tabs.setDocumentMode(True)
        tabs.setTabPosition(QTabWidget.East)
        tabs.setMovable(True)

        for n, color in enumerate(['red','green','blue','yellow']):
            tabs.addTab( Color(color), color)

        self.setCentralWidget(tabs)

As you can see, it's a little more straightforward — and a bit more attractive! You can set the position of the tabs using the cardinal directions, toggle whether tabs are moveable with .setMoveable and turn a 'document mode' on and off which (on OS X) shows a slimmer tab interface. We encounter more of these advanced widgets later.

Enjoyed this?
Then you will enjoy my book!

The hands-on guide to making desktop apps with Python. Build real apps — learn more than just the basics. Updated 2019.

Check it out

Continue on to “Actions — Toolbars & Menus”

Discussion