Reputation: 435
I'd like to pass 2 variable arguments in a function to be assigned to a Button
command
. These variables change within a for
loop that also creates the buttons.
Mostly inspired by some of the top answers here and here, here are my fail attempts at trying to solve this problem based on what I read:
I tried using partial
:
self.dct[(i, j)] = Button(command=partial(self.on_click, i, j))
Another try:
self.dct[(i, j)] = Button(command=partial(partial(self.on_click, i), j))
And another:
self.dct[(i, j)] = Button(command=partial(self.on_click, [i, j]))
.. Guess what?
tup = [i, j]
self.dct[(i, j)] = Button(command=partial(self.on_click, tup))
And then, lambda
:
self.dct[(i, j)] = Button(command=lambda i=i, j=j: self.on_click(i, j))
Here's my code:
import tkinter as tk
from functools import partial
class Board(tk.Frame):
board = None
images = None
tile = None
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.tile = {}
for i in range(10):
for j in range(10):
self.tile[(i, j)]['btn_obj'] = tk.Button(self.board, command=partial(partial(self.on_click, i), j))
def on_click(self, i, j):
print("X: {}, Y:{}".format(j, i))
partial
always causes an error like this:
TypeError: on_click() takes 2 positional arguments but 3 were given
It's always mismatched number of arguments.
Meanwhile, lambda
gets the wrong value of the variables, causing something of an error in tkinter
's part.
Upvotes: 1
Views: 1491
Reputation: 385970
The lambda in your question should work:
tk.Button(self.board, command=lambda i=i, j=j: self.on_click(i,j))
This binds the value i
and j
as the default values for the lambda parameters.
Personally I prefer lambda over partial, mainly because I don't have to import the functools module. If you wish to use partial, it should look like your first example:
tk.Button(self, command=partial(self.on_click, i, j))
In both cases, on_click
will be passed the correct value for i
and j
based on their values when the button is created.
Here is an example based on your code, but with some unnecessary code removed for clarity:
import tkinter as tk
from functools import partial
class Board(tk.Frame):
def __init__(self, parent, method):
tk.Frame.__init__(self, parent, bd=2, relief="sunken")
for i in range(10):
for j in range(10):
if method == "lambda":
button = tk.Button(self, command=lambda i=i, j=j: self.on_click(i,j))
else:
button = tk.Button(self, command=partial(self.on_click, i, j))
button.grid(row=i, column=j)
def on_click(self, i, j):
print("X: {}, Y:{}".format(j, i))
root = tk.Tk()
board1 = Board(root, method="lambda")
board2 = Board(root, method="partial")
board1.pack(side="top", fill="both", expand=True)
board2.pack(side="top", fill="both", expand=True)
root.mainloop()
Upvotes: 2