One very common issue when you start creating GUI applications with Python & Qt is having your widgets either disappear or start popping out of your interface as independent windows. This is pretty confusing if you don't understand why it happens, but once you do it's usually very easy to fix.
The key points --
- Any widget without a parent is a window. If you create a widget but don't set a parent it will become a separate window.
- Widgets without parents (i.e. any windows) are hidden by default and remain hidden until they are shown either by you, or automatically by Qt (for example when selecting tabs in a
QTabWidget
). - Adding widgets to layouts sets their parent. Widgets not in layouts must have parents set explicitly.
- Removing a widget from a layout doesn't remove its parent.
- You can remove the parent from a widget by setting the parent to
None
. This will turn it into a window!
Below is a small example to demonstrate these effects. We create a window, add a layout and a widget.
- PyQt5
- PyQt6
- PySide2
- PySide6
import sys
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QApplication
class Window(QWidget):
def __init__(self):
super().__init__()
# This is the widget that we will experiment with: note that we don't
# set a parenet, and don't add it to the layout.
self.mywidget = QLabel("Hello")
self.mywidget.show() # We need to show it (or focus it) to make it visible.
add_parent = QPushButton("Add parent")
add_parent.clicked.connect(self.add_parent)
remove_parent = QPushButton("Remove parent")
remove_parent.clicked.connect(self.remove_parent)
add_layout = QPushButton("Add layout")
add_layout.clicked.connect(self.add_layout)
remove_layout = QPushButton("Remove layout")
remove_layout.clicked.connect(self.remove_layout)
self.vlayout = QVBoxLayout()
self.vlayout.addWidget(add_parent)
self.vlayout.addWidget(remove_parent)
self.vlayout.addWidget(add_layout)
self.vlayout.addWidget(remove_layout)
self.setLayout(self.vlayout)
def add_parent(self):
self.mywidget.setParent(self)
self.mywidget.move(0,0)
self.mywidget.show()
print("Added parent, parent is:", self.mywidget.parent())
def remove_parent(self):
self.mywidget.setParent(None)
self.mywidget.show()
print("Removed parent, parent is:", self.mywidget.parent())
def add_layout(self):
self.vlayout.addWidget(self.mywidget)
print("Added layout, parent is:", self.mywidget.parent())
def remove_layout(self):
self.vlayout.removeWidget(self.mywidget)
print("Removed layout, parent is:", self.mywidget.parent())
app = QApplication(sys.argv)
w = Window()
w.show()
app.exec()
import sys
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QApplication
class Window(QWidget):
def __init__(self):
super().__init__()
# This is the widget that we will experiment with: note that we don't
# set a parenet, and don't add it to the layout.
self.mywidget = QLabel("Hello")
self.mywidget.show() # We need to show it (or focus it) to make it visible.
add_parent = QPushButton("Add parent")
add_parent.clicked.connect(self.add_parent)
remove_parent = QPushButton("Remove parent")
remove_parent.clicked.connect(self.remove_parent)
add_layout = QPushButton("Add layout")
add_layout.clicked.connect(self.add_layout)
remove_layout = QPushButton("Remove layout")
remove_layout.clicked.connect(self.remove_layout)
self.vlayout = QVBoxLayout()
self.vlayout.addWidget(add_parent)
self.vlayout.addWidget(remove_parent)
self.vlayout.addWidget(add_layout)
self.vlayout.addWidget(remove_layout)
self.setLayout(self.vlayout)
def add_parent(self):
self.mywidget.setParent(self)
self.mywidget.move(0,0)
self.mywidget.show()
print("Added parent, parent is:", self.mywidget.parent())
def remove_parent(self):
self.mywidget.setParent(None)
self.mywidget.show()
print("Removed parent, parent is:", self.mywidget.parent())
def add_layout(self):
self.vlayout.addWidget(self.mywidget)
print("Added layout, parent is:", self.mywidget.parent())
def remove_layout(self):
self.vlayout.removeWidget(self.mywidget)
print("Removed layout, parent is:", self.mywidget.parent())
app = QApplication(sys.argv)
w = Window()
w.show()
app.exec()
import sys
from PySide2.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QApplication
class Window(QWidget):
def __init__(self):
super().__init__()
# This is the widget that we will experiment with: note that we don't
# set a parenet, and don't add it to the layout.
self.mywidget = QLabel("Hello")
self.mywidget.show() # We need to show it (or focus it) to make it visible.
add_parent = QPushButton("Add parent")
add_parent.clicked.connect(self.add_parent)
remove_parent = QPushButton("Remove parent")
remove_parent.clicked.connect(self.remove_parent)
add_layout = QPushButton("Add layout")
add_layout.clicked.connect(self.add_layout)
remove_layout = QPushButton("Remove layout")
remove_layout.clicked.connect(self.remove_layout)
self.vlayout = QVBoxLayout()
self.vlayout.addWidget(add_parent)
self.vlayout.addWidget(remove_parent)
self.vlayout.addWidget(add_layout)
self.vlayout.addWidget(remove_layout)
self.setLayout(self.vlayout)
def add_parent(self):
self.mywidget.setParent(self)
self.mywidget.move(0,0)
self.mywidget.show()
print("Added parent, parent is:", self.mywidget.parent())
def remove_parent(self):
self.mywidget.setParent(None)
self.mywidget.show()
print("Removed parent, parent is:", self.mywidget.parent())
def add_layout(self):
self.vlayout.addWidget(self.mywidget)
print("Added layout, parent is:", self.mywidget.parent())
def remove_layout(self):
self.vlayout.removeWidget(self.mywidget)
print("Removed layout, parent is:", self.mywidget.parent())
app = QApplication(sys.argv)
w = Window()
w.show()
app.exec_()
import sys
from PySide6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QApplication
class Window(QWidget):
def __init__(self):
super().__init__()
# This is the widget that we will experiment with: note that we don't
# set a parenet, and don't add it to the layout.
self.mywidget = QLabel("Hello")
self.mywidget.show() # We need to show it (or focus it) to make it visible.
add_parent = QPushButton("Add parent")
add_parent.clicked.connect(self.add_parent)
remove_parent = QPushButton("Remove parent")
remove_parent.clicked.connect(self.remove_parent)
add_layout = QPushButton("Add layout")
add_layout.clicked.connect(self.add_layout)
remove_layout = QPushButton("Remove layout")
remove_layout.clicked.connect(self.remove_layout)
self.vlayout = QVBoxLayout()
self.vlayout.addWidget(add_parent)
self.vlayout.addWidget(remove_parent)
self.vlayout.addWidget(add_layout)
self.vlayout.addWidget(remove_layout)
self.setLayout(self.vlayout)
def add_parent(self):
self.mywidget.setParent(self)
self.mywidget.move(0,0)
self.mywidget.show()
print("Added parent, parent is:", self.mywidget.parent())
def remove_parent(self):
self.mywidget.setParent(None)
self.mywidget.show()
print("Removed parent, parent is:", self.mywidget.parent())
def add_layout(self):
self.vlayout.addWidget(self.mywidget)
print("Added layout, parent is:", self.mywidget.parent())
def remove_layout(self):
self.vlayout.removeWidget(self.mywidget)
print("Removed layout, parent is:", self.mywidget.parent())
app = QApplication(sys.argv)
w = Window()
w.show()
app.exec_()
In the initial state the widget isn't added to any layout and has no parent, so when the application is run you'll actually see two floating windows. Push the control buttons to experiment with adding the widget to the layout, setting a parent, clearing the parent and removing it from the layout.
Each time the state changes the current parent will be printed to the console. Notice that when you add the widget to the layout, the widget receives the Window
as it's parent. This is the normal behavior in Qt: adding a widget to a layout will set it's parent to the widget on which the layout is applied (the container widget). In this case this is the Window
itself.
Added layout, parent is: <__main__.Window(0x15413656090) at 0x000001542CB98688>
If you remove the widget from the layout, it's parent is not automatically cleared. The widget will remain inside the window, near it's original position. But the layout will re-lay the other items over it.
Create GUI Applications with Python & Qt6 by Martin Fitzpatrick — (PySide6 Edition) The hands-on guide to making apps with Python — Over 10,000 copies sold!
If you remove the parent the widget will return to it's floating state.
Note that when setting the parent on the window we also call .move(0, 0)
to move it to a specific point relative to the parent window. This is necessary to ensure we can see the widget: if you remove this, setting the parent will just make the widget disappear. If you have widgets disappearing in your apps, this is a good that they have a parent set but are not properly positioned or added to a layout. As a general rule, use layouts to avoid hitting these kinds of issues!