Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

QLayout.replace not replacing

Tags:

qt5

pyqt5

pyside2

I have the following code to replace a widget (self.lbl) each time I click on a button (self.btn):

import sys
from PySide2.QtCore import Slot
from PySide2.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget, \
        QPushButton


class Workshop(QWidget):

    def __init__(self):
        super().__init__()
        self.n = 0
        self.btn = QPushButton('Push me')
        self.lbl = QLabel(str(self.n))

        self.main_layout = QVBoxLayout()
        self.sub_layout = QVBoxLayout()
        self.sub_layout.addWidget(self.lbl)
        self.sub_layout.addWidget(self.btn)
        self.main_layout.addLayout(self.sub_layout)

        self.btn.clicked.connect(self.change_label)
        self.setLayout(self.main_layout)
        self.show()

    @Slot()
    def change_label(self):
        new_label = QLabel(str(self.n + 1))
        self.main_layout.replaceWidget(self.lbl, new_label)
        self.n += 1
        self.lbl = new_label


if __name__ == '__main__':
    app = QApplication()

    w = Workshop()

    sys.exit(app.exec_())

Right after its initialization, the object w looks like this: enter image description here

When I click on the "Push me" button (self.btn), the number is incremented as wanted, but the initial "0" remains in the background:

enter image description here

But the other numbers do not however remain in the background ; only "0" does. Fore example, here is "22" (result after I clicked 22 times on "Push me"):

enter image description here

Note: I know that I could achieve the resultant I want with the setText method, but this code is just a snippet that I will adapt for a class in which I will not have a method like setText.

Thank you!

like image 523
bcusk07q Avatar asked Mar 04 '26 15:03

bcusk07q


1 Answers

When you replace the widget in the layout, the previous one still remains there.
From replaceWidget():

The parent of widget from is left unchanged.

The problem is that when a widget is removed from a layout, it still keeps its parent (in your case, the Workshop instance), so you can still view it. This is more clear if you set the alignment to AlignCenter for each new QLabel you create: you'll see that if you add a new label and resize the window, the previous one will keep its previous position:

class Workshop(QWidget):

    def __init__(self):
        # ...
        self.lbl = QLabel(str(self.n), alignment=QtCore.Qt.AlignCenter)
        # ...

    def change_label(self):
        new_label = QLabel(str(self.n + 1), alignment=QtCore.Qt.AlignCenter)
        # ...

You have two possibilities, which are actually very similar:

  • set the parent of the "removed" widget to None: the garbage collector will remove the widget as soon as you overwrite self.lbl:
    self.lbl.setParent(None)
  • remove the widget by calling deleteLater() which is what happens when reparenting a widget to None and, if it has no other persisting references, gets garbage collected:
    self.lbl.deleteLater()

For your pourposes, I'd suggest you to go with deleteLater(), as calling setParent() (which is a reimplementation of QObject's setParent) actually does lots of other things (most importantly, checks the focus chain and resets the widget's window flags), and since the widget is going to be deleted anyway, all those things are actually unnecessary, and QObject's implementation of setParent(None) would be called anyway.

The graphic "glitch" you are facing might depend on the underlying low-level painting function, which has some (known) unexpected behaviors on MacOS in certain cases.

like image 61
musicamante Avatar answered Mar 07 '26 05:03

musicamante