Search code examples
pythonpyqtpyqt5qwidgetqslider

Adding/Removing QSlider widgets on PyQt5


I want to dynamically change the number of sliders on my application window, in dependence of the number of checked items in a QStandardItemModel structure.

My application window has an instance of QVBoxLayout called sliders, which I update when a button is pressed:

first removing all sliders eventually in there:

self.sliders.removeWidget(slider)

And then creating a new set.

The relevant code:

def create_sliders(self):
    if len(self.sliders_list):
        for sl in self.sliders_list:
            self.sliders.removeWidget(sl)
    self.sliders_list = []
    for index in range(self.model.rowCount()):
        if self.model.item(index).checkState():
            slid = QSlider(Qt.Horizontal)
            self.sliders.addWidget(slid)
            self.sliders_list.append(slid)

The principle seems to work, however what happens is weird as the deleted sliders do not really disappear but it is as they were 'disconnected' from the underlying layout.

When created, the sliders keep their position among other elements while I resize the main window. However, once they've been removed, they occupy a fixed position and can for instance disappear if I reduce the size of the window. Unfortunately I'm having difficulties in linking images (it says the format is not supported when I try to link from pasteboard), so I hope this description is enough to highlight the issue.

Do I have to remove the sliders using a different procedure?

EDIT

Thanks to @eyllansec for his reply, it condenses a bunch of other replies around the topic, which I wasn't able to find as I did not know the method deleteLater() which is the key to get rid of widgets inside a QLayout.

I am marking it as my chosen (hey, it's the only one and it works, after all!), however I want to propose my own code which also works with minimal changes w.r.t. to what I proposed in the beginning.

The key point here is that I was using the metod QLayout.removeWidget(QWidget) which I was wrongly thinking, it would..er..Remove the widget! But actually what it does is (if I understood it right) remove it from the layout instance.

That is why it was still hanging in my window, although it seemed disconnected

Manual reference

Also, the proposed code is far more general than what I need, as it is a recursion over layout contents, which could in principle be both other QLayout objects or QWidgets (or even Qspacer), and be organized in a hierarchy (i.e., a QWidget QLayout within a QLayout and so on).

check this other answer

From there, the use of recursion and the series of if-then constructs.

My case is much simpler though, as I use this QVLayout instance to just place my QSliders and this will be all. So, for me, I stick to my list for now as I do not like the formalism of QLayout.TakeAt(n) and I don't need it. I was glad that the references I build in a list are absolutely fine to work with.

In the end, this is the slightly changed code that works for me in this case:

def create_sliders(self):
    if len(self.sliders_list):
        for sl in self.sliders_list:
            sl.deleteLater()
    self.sliders_list = []
    for index in range(self.model.rowCount()):
        if self.model.item(index).checkState():
            slid = QSlider(Qt.Horizontal)
            self.sliders.addWidget(slid)
            self.sliders_list.append(slid)

Solution

  • It is not necessary to save the sliders in a list, they can be accessed through the layout where it is contained. I have implemented a function that deletes the elements within a layout. The solution is as follows:

    def create_sliders(self):
        self.clearLayout(self.sliders)
    
        for index in range(self.model.rowCount()):
            if self.model.item(index).checkState():
                slid = QSlider(Qt.Horizontal)
                self.sliders.addWidget(slid)
    
    def clearLayout(self, layout):
        if layout:
            while layout.count():
                item = layout.takeAt(0)
                widget = item.widget()
                if widget:
                    widget.deleteLater()
                else :
                    self.clearLayout(item.layout())
                layout.removeItem(item)