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:
1.2 Box Layout
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:
1.3 Grid Layout
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:
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
Adding layouts does the same thing, except that it changes
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.
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
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
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
In the following code, I add a grid Layout to the
It’s worth noting that there are two ways to remove a widget. They are
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