Generate Tkinter Buttons dynamically
I want to generate n
amount of Tkinter Button
s which do different things. I have this code:
import Tkinter as tk
for i in range(boardWidth):
newButton = tk.Button(root, text=str(i+1),
command=lambda: Board.playColumn(i+1, Board.getCurrentPlayer()))
Board.boardButtons.append(newButton)
If boardWidth
is 5, though I get buttons labelled 1
to 5
, when clicked they all do Board.playColumn(5, Board.getCurrentPlayer())
.
I need the first button to do Board.playColumn(1, Board.getCurrentPlayer())
, the second to do Board.playColumn(2, Board.getCurrentPlayer())
and so on.
Solution 1:
I think the problem is that the lambda
is picking up the final value of i
after the for
loop ends. This should fix that (untested):
import Tkinter as tk
for i in range(boardWidth):
newButton = tk.Button(root, text=str(i+1),
command=lambda j=i+1: Board.playColumn(j, Board.getCurrentPlayer()))
Board.boardButtons.append(newButton)
Update
BTW, this worked by adding an argument to the lambda
function with a default value calculated from the value of i
at the time each one is created in the loop rather than referring back to the final value of i
through a closure when the expression within it executes later.
Solution 2:
Your problem is that you create lots of lambda
objects in the same namespace, and those lambda
s make reference to names in the outer scope. That means they don't become closures and they don't store references to the objects until later... When it happens, all lambdas will refer to the last value of i
.
Try using a callback factory to fix that:
import Tkinter as tk
def callbackFactory(b, n):
def _callback():
return b.playColumn(n, b.getCurrentPlayer())
return _callback
for i in range(boardWidth):
newButton = tk.Button(root, text=str(i+1),
command=callbackFactory(Board, i+1))
Board.boardButtons.append(newButton)
Another idea is to store the current value of i as a default argument value in the lambda
object, instead of relying on closure behavior to store the reference:
for i in range(boardWidth):
newButton = tk.Button(root, text=str(i+1),
command=lambda x=i: Board.playColumn(x+1, Board.getCurrentPlayer()))
Board.boardButtons.append(newButton)