0

So I have the code below to generate a whole bunch of widgets iteratively from a list of names and other inputs. My problem now is with the button widgets, which have all their commands being the same. The problem with the code is obvious, the command code for each button doesn't run until the loop finish, thus all buttons call the same command as the last/"i-th" button.

So my question would be how do I have each button call the right associated command instead of having the same command as the button in the last row, and still have my widgets be generated iteratively.

# Generates a frame that also contains all the necessary widgets for all mounts in a list
# Takes the parent window the frame will be part of (TK()),
# the row (int) and column (int) the new frame will be located on the parent window,
# the name of the frame we're creating (string),
# and a list of all the mounts to go into the frame (list of string),
# the complement_checkbox (tkinter.CheckButton())
# Returns (tkinter.LabelFrame)
def generate_widgets(master, frame_name, mount_list, complement_checkbox):
    new_frame = LabelFrame(master, text = frame_name, font=("Arial Narrow", 18), padx = 5, pady = 5)
    new_frame.grid(row = 0, column = 0, padx = 10, pady = 10, sticky = N + S + E + W)

    # We have row weight be equal to the number of mounts per frame
    master.rowconfigure(0, weight = len(mount_list))
    master.columnconfigure(0, weight = 1)

    label_widgets = {}
    attempts_string = {}
    spin_widgets = {}
    button_widgets = {}
    for i in range(len(mount_list)):

        full_name = mount_list[i]
        mount_name = full_name.split(' - ')[1]

        label_widgets[i] = Label(new_frame, text = mount_list[i], font = ("Arial Narrow", 12))
        label_widgets[i].grid(row = i, column = 0, sticky = W)

        attempts_string[i] = StringVar()
        attempts_string[i].set(load_attempts(mount_name))

        spin_widgets[i] = Spinbox(new_frame, from_ = 0, to = 255, width = 5, textvariable = attempts_string[i])
        spin_widgets[i].grid(row = i, column = 1, sticky = E)

        button_widgets[i] = Button(new_frame,
                                   text = "Calculate",
                                   command = lambda: open_and_save(full_name,
                                                                   mount_name,
                                                                   int(spin_widgets[i].get()),
                                                                   complement_checkbox.get()))
        button_widgets[i].grid(row = i, column = 2, sticky = E)

        new_frame.rowconfigure(i, weight = 1)

    # Column 0 is label, column 1 is spinbox, column 2 is button
    new_frame.columnconfigure(0, weight = 1)
    new_frame.columnconfigure(1, weight = 1)
    new_frame.columnconfigure(2, weight = 1)

    return new_frame
Verzus
  • 1

2 Answers2

0

Use default arguments for lambda, see example:

command = lambda f=full_name, m=mount_name:     open_and_save(f, m)
#                <-- default arguments -->   <- function definition ->

This example only uses two of the arguments, but I think you can get the general idea.

figbeam
  • 7,001
  • 2
  • 12
  • 18
0

I ended up using command = lambda j = i, which saves i to j the moment the lambda is defined, then using j for the inputs of open_and_save

Verzus
  • 1