PySide – QStackedLayout.setCurrentIndex() does not work as expected when it's inside a for-loop

Question:

I’m implementing a UI, which it contains several buttons on top, and there’s a QStackedLayout object under those buttons, the purpose of clicking on different buttons should automatically switch to its associated QWidget, which is inside the QStackedLayout object. And the amount of buttons and their associated QWidget is dynamically added.

For example, here I want to implement a simple window that holds two buttons ‘a’ and ‘b’, ideally when clicking on the’a’ button, you should see the label ‘a’ below, which means the QStackedLayout is set to an appropriate widget that holds the QLabel ‘a’, similar rules should apply for button ‘b’ as well. so I wrote the following codes:

import sys

from PySide.QtCore import *
from PySide.QtGui import * 

class main_gui(QWidget):

    def __init__(self, parent = None):
        super(main_gui, self).__init__(parent)

        #create a root level widget 
        self.main_widget = QWidget()
        #create a root level layout that holds all the child widgets
        self.main_layout = QVBoxLayout(self.main_widget)      

        # widgets for buttons layout
        self.asset_type_buttons_widget = QWidget()
        # layout for all buttons
        self.asset_type_buttons_layout = QHBoxLayout(self.asset_type_buttons_widget)

        # now create the stack layout
        self.asset_stacked_widget = QWidget()
        self.asset_stacked_layout = QStackedLayout(self.asset_stacked_widget)

        # add the buttons and stack layout into the root level layout
        self.main_layout.addWidget(self.asset_type_buttons_widget)
        self.main_layout.addWidget(self.asset_stacked_widget)    

        #dynamically adds buttons and stacked widgets, here we're going to add 2 buttons, which their labels are 'a' and 'b'
        self.create_asset_sections(self.asset_stacked_layout, self.asset_type_buttons_layout, 'a','b')    

        self.main_widget.show()

    def create_asset_sections(self, stack_layout, parent_buttons_layout, *asset_types):
        # asset_types represents any arbitrary number of buttons and their associated stacked widgets
        for type in asset_types:

            index = asset_types.index(type)

            self.type_top_button = QPushButton(type)

            parent_buttons_layout.addWidget(self.type_top_button)     

            self.type_top_widget = QWidget()
            self.type_top_layout = QVBoxLayout(self.type_top_widget)

            i = stack_layout.insertWidget(index, self.type_top_widget)   

            self.test_label = QLabel(type)
            self.type_top_layout.addWidget(self.test_label)

            self.type_top_button.clicked.connect(lambda: stack_layout.setCurrentIndex(i))

app = QApplication(sys.argv)         
gui = main_gui()          
app.exec_()

But executed above codes you should notice that label ‘a’ only appears once, then no matter you click on button ‘a’ or button ‘b’, label ‘b’ is always being shown under the buttons.

I also tried to modified the codes then used QStackedLayout.setCurrentWidget() method, and here’s another version of create_asset_sections()

def create_asset_sections(self, stack_layout, parent_buttons_layout, *asset_types):
    # create a dictionary that holds the buttons and their associated stack widgets
    self.map_buttons_widgets = {}

    for type in asset_types:

        self.type_top_button = QPushButton(type)

        parent_buttons_layout.addWidget(self.type_top_button)     

        self.type_top_widget = QWidget()
        self.type_top_layout = QVBoxLayout(self.type_top_widget)

        stack_layout.addWidget(self.type_top_widget)   

        self.test_label = QLabel(type)
        self.type_top_layout.addWidget(self.test_label)

        self.map_buttons_widgets[self.type_top_button] = self.type_top_widget

    for btn, wigt in self.map_buttons_widgets.iteritems():

        btn.clicked.connect(lambda: stack_layout.setCurrentWidget(wigt))

So use above methods to replace the previous one doesn’t help achieve my original purpose either.

Any thoughts? Thank you for your answers in advance.

Asked By: PREDATOR

||

Answers:

See this question. It’s a different question, but they’re having essentially the same problem. This line is the issue

self.type_top_button.clicked.connect(lambda: stack_layout.setCurrentIndex(i))

Specifically

lambda: stack_layout.setCurrentIndex(i)

The problem is that the value of i is not evaluated when you create the lambda function. It’s evaluated when the lambda function is called, and by that point, the loop has finished and every lambda will be using the exact same value of i at the end of the loop, not the value of i when the lambda was created.

You can fix this two ways. The first is to use default arguments in your lambda, which forces the value of i to be evaluated at function creation time.

lambda i=i: stack_layout.setCurrentIndex(i) 

Alternatively, you can use functools.partial

from functools import partial

self.type_top_button.clicked.connect(partial(layout.setCurrentIndex, i))
Answered By: Brendan Abel
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.