简体   繁体   中英

Binding many buttons to a function, passing each button's name as an argument

Note that my problem is the opposite of this: Creating functions in a loop in that I have many buttons and one function, not many functions.

I create 10 numbered buttons from a for loop, then try to bind each one to a function that will print the button's number; See code below:

import tkinter as tk

class Window(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)

        # creating buttons and adding them to dictionary

        self.buttons = {}
        for number in range(1, 11):
            self.buttons.update({'button' + str(number): tk.Button(self, height=1, width=4, bg="grey", text=number)})

        # example of a pair in the dictionary: 'button2': <Tkinter.Button instance at 0x101f9ce18>


        """ bind all the buttons to callback, each button is
            named something like 'button3',so I take the number off
            the end of its name and feed that as an argument to Callback"""

        for button in self.buttons:
            self.buttons[button].bind('<Button-1>', lambda event: self.Callback(event, button[6:]))
            self.buttons[button].pack(side='left')

    def Callback(self, event, num):
        print(num)

All the buttons appear on the window no problem, but when I click any of them, the console prints ' 10 ', as opposed to the button's number. It seems the function is only remembering the last argument it was given.

First lets correct your code to give the desired answer.

import tkinter as tk

class Window(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)

        self.buttons = {}
        for number in range(1, 11):
            self.buttons.update({'button' + str(number): tk.Button(self, height=1, width=4, bg="grey", text=number)})

        for button in self.buttons:
            self.buttons[button].bind('<Button-1>', lambda event, : self.Callback(event, num))
            self.buttons[button].pack(side='left')               #\____________/

    def Callback(self, event, num):
        print(num)

Window().mainloop()

Explanation :

The trick lies in the way lambda functions work.

When you write lambda event: self.Callback(event, button[6:]) , it doesn't get the value of button[6:] at that instance and store it. Instead, it makes a closure , which is sort of like a note to itself saying " I should look for what the value of the variable button(the iterator) is at the time that I am called ".

Now when the loop is over and every widget is ready and set up, and you call it, it will look for the value of button at that time, which is ofcourse the last value of the iteration (here, button10 ).

num=button[6:] causes the function to store the current value of the counter(here button ) at the time your lambda is defined, instead of waiting to look up the value of button later.

Credits: BrenBarn


Just to add, you can do what you are doing right now in much less code using the command attribute of Button widget. Here is an example.

import tkinter as tk

class Window(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)

        for number in range(1, 11):
            tk.Button(self, height=1, width=4, bg="grey", text=number, command=lambda num=number: self.Callback(num)).pack(side='left')

    def Callback(self, num):
        print(num)

Window().mainloop()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM