Using Qt5 I am trying to make a widget work using absolute positioning. The code below is a minimum working example of something I am trying to do. A quick walk through of the code:
CentralWidget
is the central widget of the main window and holds MyWidget
using absolute positioning, e.g. without using any layouts. MyWidget
does not set its child widgets immediately but provides a SetData
method which first removes all current child widgets and then adds the new child widgets to its layout.SetData
is triggered using a timer in the main window.I commented out two lines of code. The first "enables" relative positioning using layouts by adding a layout to CentralWidget
. This line shows what I am trying to achieve but with absolute positioning. The second comment enables some debug information:
MyWidget
layout.count: 3
size: PyQt5.QtCore.QSize(-1, -1)
sizeHint: PyQt5.QtCore.QSize(200, 100)
CentralWidget
size: PyQt5.QtCore.QSize(18, 18)
sizeHint: PyQt5.QtCore.QSize(18, 18)
What I am doing wrong in order for MyWidget
to be visible using absolute positioning?
Code:
from PyQt5 import QtCore, QtWidgets
import sys
class MyWidget(QtWidgets.QWidget):
z = 0
def __init__(self, parent):
super().__init__(parent)
self._layout = QtWidgets.QVBoxLayout(self)
def SetData(self):
while self._layout.count() > 0:
widget = self._layout.takeAt(0).widget()
widget.hide()
widget.deleteLater()
for i in range(3):
self._layout.addWidget(QtWidgets.QLabel(str(MyWidget.z * 10 + i)))
MyWidget.z += 1
class CentralWidget(QtWidgets.QWidget):
def __init__(self, parent):
super().__init__(parent)
self._myWidget = MyWidget(self)
# QtWidgets.QHBoxLayout(self).addWidget(self._myWidget)
def SetData(self):
self._myWidget.SetData()
# print("MyWidget\n layout.count: {}\n size: {}\n sizeHint: {}\n\nCentralWidget\n size: {}\n sizeHint: {}\n\n".format(self._myWidget.layout().count(), self.sizeHint(), self.size(), self._myWidget.sizeHint(), self._myWidget.size()))
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
centralWidget = CentralWidget(self)
self.setCentralWidget(centralWidget)
self._timer = QtCore.QTimer(self)
self._timer.timeout.connect(centralWidget.SetData)
self._timer.start(500)
def main():
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
app.exec_()
if __name__ == "__main__":
main()
The reason for this behavior is directly related to the fact that the widget is not added to a layout and its contents are added after being shown.
In fact, if you call centralWidget.SetData()
upon initialization and before mainWindow.show()
, it will work as expected.
A lot of things happen when you add a child widget to a layout, and this usually involves multiple calls to the children size hints, allowing the parent to adapt its own size hint, and, after that, adapt its size and that of its children.
If that "container widget" is itself contained in another layout, that widget will be automatically resized (based on its hint) in the next cycle of events, but this doesn't happen in your case, since yours is a "free" widget.
The function you are looking for is QWidget.adjustSize()
, but, for the aforementioned reasons, you cannot call it immediately after adding the children widgets.
To overcome your issue, you can call QApplication.processEvents()
before adjustSize()
, or, eventually, use a 0-based single shot QTimer:
def SetData(self):
while self._layout.count() > 0:
widget = self._layout.takeAt(0).widget()
widget.hide()
widget.deleteLater()
for i in range(3):
self._layout.addWidget(QtWidgets.QLabel(str(MyWidget.z * 10 + i)))
MyWidget.z += 1
QtCore.QTimer.singleShot(0, self.adjustSize)