How to dynamically add and remove widgets and layouts in PyQt5

How to dynamically add and remove widgets and layouts in PyQt5

    • 1. Layout management in PyQt5
      • 1.1 Absolute positioning
      • 1.2 Box Layout
      • 1.3 Grid Layout
    • 2. dynamically add widgets and layouts
    • 3. dynamically remove widgets and layouts
      • 3.1 remove widgets
      • 3.2 remove layouts

using PyQt, I am trying to create an interface for which I can add or remove widget dynamically. It took me some time to finish the task, so I wanted to write it down. This tutorial is divided into three sections: the first section introduces PyQt’s layout management; Section 2 shows how to add widgets dynamically; The last section describes how to remove widgets.

1. Layout management in PyQt5

Layout management is the way how we place widgets (widget is the name given to a component of the UI that the user can interact with. User interfaces are made up of multiple widgets, arranged within the window.) in the application window.

1.1 Absolute positioning

In this approach, we specify the location and size (in pixels) of widgets when we create them. For this reason, this approach has the following limitations:

  • If we resize the main window, the size and location of widgets in the window will not change;
  • The UI may look different on different platforms;
  • Changing fonts in the application may break the layout
  • If we decide to change our layout, we have to completely redo our layout, which means there is no dynamic way to add and remove widgets

The following example positions widgets in absolute coordinates.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import sys
from PyQt5.QtWidgets import QWidget, QPushButton,QHBoxLayout, QGridLayout, QApplication

class UI(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
     
        #We use the move() method to position our widgets. In our case these are labels. We position them by providing the x and y coordinates.

        button1 = QPushButton('Liu',self)
        button1.move(15, 10)

        button2 = QPushButton('Chen',self,)
        button2.move(35, 40)

        button3 = QPushButton('Xiiiiii',self)
        button3.move(55, 70)

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Absolute')
        self.show()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = UI()
    sys.exit(app.exec_())

The result of the above code is shown in the figure below:
absolute

1.2 Box Layout

QHBoxLayout and QVBoxLayout are basic layout classes that line up widgets horizontally and vertically.

Let’s say we want to place two buttons on the same horizontal line in the top right corner of the UI. We can start by setting a layout, cutting it horizontally, and adding widgets with two buttons at the right. Then cut the layout vertically and add our layout to the top. The code is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import sys
from PyQt5.QtWidgets import QWidget, QPushButton,QHBoxLayout, QVBoxLayout, QApplication

class UI(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):

        Button_1 = QPushButton("1")
        Button_2 = QPushButton("2")

        #We create a horizontal box layout and add a stretch factor and both buttons. The stretch adds a stretchable space before the two buttons. This will push them to the right of the window.
        hbox = QHBoxLayout()
        hbox.addStretch(1)
        hbox.addWidget(Button_1)
        hbox.addWidget(Button_2)

        #The horizontal layout is placed into the vertical layout. The stretch factor in the vertical box will push the horizontal box with the buttons to the top of the window.
        vbox = QVBoxLayout()
        vbox.addLayout(hbox)
        vbox.addStretch(1)
       

        self.setLayout(vbox)

        self.setGeometry(300, 300, 300, 150)
        self.setWindowTitle('Box')
        self.show()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = UI()
    sys.exit(app.exec_())

The result of box layout is shown in the figure:
box

1.3 Grid Layout

QGridLayout is the most universal layout class. It divides the space into rows and columns.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import sys
from PyQt5.QtWidgets import QWidget, QPushButton,QHBoxLayout, QGridLayout, QApplication

class UI(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):

        grid = QGridLayout()
        self.setLayout(grid)

        #labels used later for buttons
        names = ['1','2',
                '3','4',
                '5','6']

        #a list of positions in the grid.
        positions = [(i, j) for i in range(3) for j in range(2)]

        #Buttons are created and added to the layout with the addWidget() method.
        for position, name in zip(positions, names):
            button = QPushButton(name)
            grid.addWidget(button, *position)

        self.setWindowTitle('Grid')
        self.show()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = UI()
    sys.exit(app.exec_())

The result of grid layout is shown in the figure:
grid

2. dynamically add widgets and layouts

Actually all we need to do is to create a function which we will add widgets into the layout within this function. We also need to set a button to trigger this function. The following code adds three new buttons via an “Add” button using add function.

Adding layouts does the same thing, except that it changes addWidget() to addLayout()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import sys
from PyQt5.QtWidgets import QWidget, QPushButton,QHBoxLayout, QGridLayout, QApplication

class UI(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.addButton = QPushButton("add")
        #once click the addButton, call add() function
        self.addButton.clicked.connect(self.add)

        self.layout = QGridLayout()
        self.layout.addWidget(self.addButton, 0, 0)

        self.setLayout(self.layout)

        self.setWindowTitle('add')
        self.show()

    def add(self):
        Button1 = QPushButton("1")
        self.layout.addWidget(Button1, 0, 1)

        Button2 = QPushButton("2")
        self.layout.addWidget(Button2, 1, 0)

        Button3 = QPushButton("3")
        self.layout.addWidget(Button3, 1, 1)



if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = UI()
    sys.exit(app.exec_())

The following image shows the result of the above code. (a) is the initial effect, (b) is the effect after pressing add button. We have added three buttons to the upper right corner, lower left corner, and lower right corner, respectively.
add

3. dynamically remove widgets and layouts

3.1 remove widgets

For deleting widgets, we can use the following method to dynamically delete them:

1
2
3
self.layout.itemAt(i).widget().deleteLater()
1
#i is the index of the widget that you want to delete

The following code removes the widgets added by the add button using a delete button. Similarly, as with Add, we will have the “DELETE” button to trigger the delete function to dynamically delete widgets:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import sys
from PyQt5.QtWidgets import QWidget, QPushButton,QHBoxLayout, QGridLayout, QApplication

class UI(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.addButton = QPushButton("add")
        #once click the addButton, call add() function
        self.addButton.clicked.connect(self.add)

        self.delButton = QPushButton("delete")
        self.delButton.clicked.connect(self.delete)

        self.layout = QGridLayout()
        self.layout.addWidget(self.addButton, 0, 0)
        self.layout.addWidget(self.delButton, 0, 1)

        self.setLayout(self.layout)

        self.setWindowTitle('add')
        self.show()

    def add(self):
        Button1 = QPushButton("1")
        self.layout.addWidget(Button1, 1, 0)

        Button2 = QPushButton("2")
        self.layout.addWidget(Button2, 1, 1)

    def delete(self):
        #the indexes of widgets we want to remove is 2 and 3
        self.layout.itemAt(2).widget().deleteLater()
        self.layout.itemAt(3).widget().deleteLater()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = UI()
    sys.exit(app.exec_())

The following image shows the result of the above code. (a) is the initial effect, (b) is the effect after pressing add button, we have added two buttons to the lower left and right corner, respectively. ? The effect after deleting the two buttons
delete_widgets

3.2 remove layouts

However, this method doesn’t work when we want to delete layout. For Layout, we need to iteratively delete each widget in the layout, as shown in deleteItemsOfLayout function.

In the following code, I add a grid Layout to the add function, and then delete the added layout by calling deleteItemsOfLayout. In the deleteItemsOfLayout function, we need to first check whether the new Layout has been created. If not, we simply do not execute the function.

It’s worth noting that there are two ways to remove a widget. They are deleteLater() and setParent(None). A detailed description of the two approaches and the differences can be found in this link.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import sys
from PyQt5.QtWidgets import QWidget, QPushButton,QHBoxLayout, QGridLayout, QApplication

class UI(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.addButton = QPushButton("add")
        #once click the addButton, call add() function
        self.addButton.clicked.connect(self.add)

        self.delButton = QPushButton("delete")
        self.delButton.clicked.connect(self.deleteItemsOfLayout)

        self.layout = QGridLayout()
        self.layout.addWidget(self.addButton, 0, 0)
        self.layout.addWidget(self.delButton, 0, 1)

        self.setLayout(self.layout)

        self.setWindowTitle('add')
        self.show()

    def add(self):

        Button1 = QPushButton("1")
        Button2 = QPushButton("2")

        self.added = QGridLayout()
        self.added.addWidget(Button1, 0, 0)
        self.added.addWidget(Button2, 0, 1)

        self.layout.addLayout(self.added, 1, 0)

    #this will not work with layout
    def delete(self):
        self.layout.itemAt(2).layout().deleteLater()

    #
    def deleteItemsOfLayout(self):
        #first check whether the layout we want to delete exists
        if self.layout.count() > 2:
            #delete each widget in the layout one by one
            while self.added.count():
                item = self.added.takeAt(0)
                widget = item.widget()
                if widget is not None:
                    #two ways to delete widgets
                    #widget.setParent(None)
                    widget.deleteLater()
                else:
                    deleteItemsOfLayout(item.layout())
        else:
            pass


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = UI()
    sys.exit(app.exec_())

The following image shows the result of the above code. (a) is the initial effect and (b) is the effect after pressing add button. We have added the layout with two buttons to the lower left corner. ( c) is the effect of deletion using the deleteLater() function. (d) for the effect of deletion using the setParent(None) function
delete_layouts