user3124924
user3124924

Reputation: 31

Python Tkinter, setting up button callback functions with a loop

I'm writing a program that displays a grid of buttons, when a button is pressed I want it to print the location of the button in the grid ("row column") out to the console. Here is what I have

import Tkinter as tk

class board(tk.Tk):
    def __init__(self, parent=None):
        tk.Tk.__init__(self,parent)
        self.rows = 5
        self.columns = 5
        self.init_board()

    def init_board(self):
        for i in range(self.rows):
            for j in range(self.columns):
                cmd = lambda: self.button_callback(i,j)
                b = tk.Button(self, text=str("  "), command=cmd)
                b.grid(row=i, column=j)

    def button_callback(self, row, col):
        print(str(row) + " " + str(col))


if __name__ == '__main__':
    board().mainloop()

the problem is that when I click on any of the buttons I get "4 4" printed out which is the location of the last button instantiated in the loop. I don't know why this is happening, please help!

Upvotes: 3

Views: 1166

Answers (2)

OverShifted
OverShifted

Reputation: 515

As an addition to what @falsetru has said, You can also use functools.partial.

from functools import partial
# ...
cmd = partial(self.button_callback, i, j)
# ...

Or you can "emulate" partial's behavior like this: (use this snippet instead of the import statement)

def partial(fn, i, j):
    def nested():
        fn(i, j)
    return nested

A better "emulation" can be found here.

Upvotes: 0

falsetru
falsetru

Reputation: 369074

In the function created using lambda, i, j refers to the varaible in the init_board function, which are set to 4, 4 after the for loop ends.

You can workaround this using default argument.


Replace the following line:

cmd = lambda: self.button_callback(i,j)

with:

cmd = lambda i=i, j=j: self.button_callback(i,j)

Upvotes: 7

Related Questions