Assign a value to a button in tkinter - python

I am writing a chess program where each square is a button. Right now I am writing the command to make the pieces be able to move (which is executed when one is clicked) but it is telling me I am missing an argument (squareposition) when clicking.
How can I bind a value to each button within Game.movepiece ?
here is the code I used to create the buttons:
def drawboard(self):
x=0
y=0
for column in range(self.n):
self.changecolours()
x=x+1
y=0
for row in range(self.n):
y=y+1
colour = self.colours[self.colourindex]
position=(x,9-y)
buttons=(tk.Button(self.boardframe, padx=10, text=self.placepieces(position), bg=colour, borderwidth=2, relief="solid", font=self.piecefont, command=lambda:Game.movepiece(position) ))
buttons.grid(column=(x-1), row=(y-1), sticky="W"+"E"+"N"+"S" )
self.changecolours()
and here is the button's command function:
def movepiece(self, squareposition):
if self.square==(-10,-10):
self.square=squareposition

Game.movepiece(position) is calling the method at the class level, so position is used for the self parameter then squareposition is missing. You need to use an instance of Game to be able to call the method properly. Maybe you already instantiate it elsewhere in your code ?
Then as #Reblochon Masque said you can replace the command with command=lambda pos=position :self.movepiece(pos)
def __init__():
self.game = Game()
def drawboard(self):
[...]
position = (x,9-y)
buttons = tk.Button(self.boardframe, padx=10, text=self.placepieces(position),
bg=colour, borderwidth=2, relief="solid", font=self.piecefont,
command=lambda pos=position :self.movepiece(pos))

Related

Tkinter pass a button as argument

I'm new to Tkinter and as my first project I wanted to create a Tic Tac Toe.
I want to create 9 buttons, that will change their background image when I click on them, the problem is that I dont want to create a function for every single button but one function that will take the button in argument and will change its background image.
The code I wrote:
def play(bid):
if player == "X":
bid.config(image=cross)
if player == "O":
bid.config(image=circle)
b1 = tk.Button(app, text="", image=white, command=lambda id=b1: play(id))
b1.grid(column=0, row=0)
How can I pass b1 as an argument to play() function?
Thanks
I tried to use b1 as an argument to play(), and use play() to change b1's image.
When I try to run this code I get "name b1 is not defined".
Define a single function to create, configure, and bind your button; then your callback can close over the necessary variable referring to your button. Something like
def play(bid):
if player == "X":
bid.config(image=cross)
if player == "O":
bid.config(image=circle)
def add_button(app, r, c):
b = tk.Button(app, text="", image=white)
b.config(command=lambda: play(b))
b.grid(column=c, row=r)
return b
for row in [0,1,2]:
for col in [0,1,2]:
# Save the return value somewhere if necessary
addButton(app, row, col)
The lambda expression contains a free variable b, which refers to the variable b in the closest enclosing scope, which is the call to add_button where the lambda expression is evaluated.
One way you can do it is to separate the creation of the button from the assignment using the .config method.
b1 = tk.Button(app, text="", image=white)
b1.config(command=lambda btn_id=b1: play(btn_id))
This is not really the best way to go about it, it's better to instead pass something that defines the button like an Enum or text.
b1 = tk.Button(app, text="", image=white, command=lambda : play("b1"))
then check that "b1" is the input in your function, this way b1 can be redefined as what would happen in loops.
Here is what I wrote at my leisure, it may be useful. Just create a folder 'icons' and put 100x100 px images there
from tkinter import *
import random
class Main:
def __init__(self):
self.root = Tk()
#self.root.geometry('900x100')
def run(self):
self.variables()
self.interface()
self.root.mainloop()
def variables(self):
self.PHOTO_COUNTER = 0
self.photo_list = [
PhotoImage(file="icons/bublegum.png"),
PhotoImage(file="icons/fin.png"),
PhotoImage(file="icons/jake.png"),
PhotoImage(file="icons/marcelin.png"),
PhotoImage(file="icons/navel.png"),
PhotoImage(file="icons/winter_king.png"),
]
def interface(self):
self.Buttons = []
for i in range(10):
item = random.choice(self.photo_list)
self.btn = Button(self.root, image=item, command=lambda c=i: self.click(c))
self.btn.pack(fill=BOTH, expand=1, side=LEFT)
self.Buttons.append(self.btn)
def click(self, i):
btn = self.Buttons[i]
item = random.choice(self.photo_list)
btn.config(image=item)
A = Main()
A.run()

How to pass parameters to functions in python

I have this simple program that I wrote so I could better understand the 'return' function and how to pass a value from one function to another. All this program does is to pass the value of buttontwo=2 to the function button_one_function,so if button two is pressed first then button one does nothing.I thought that I could do this without using a global statement - is there a way of writing the code below without using global? I have tried doing this by putting the value of buttontwo in to the button_one_function parentheses but this didnt work. Thanks for any tips
from tkinter import *
my_window = Tk()
my_frame = Frame(my_window, height=500, width=500, bd='4')
my_frame.grid(row=0, column=0)
def button_one_function():
if button_two == 2:
print('do nothing')
else:
label_one = Label(my_frame, text='label one')
label_one.grid(row=1, column=0, sticky='n')
def button_two_function():
global button_two
button_two = 2
label_two = Label(my_frame, text='label two')
label_two.grid(row=1, column=1, sticky='n')
return button_two
button_one = Button(my_frame, text='button1', command=button_one_function)
button_one.grid(row=0, column=0)
button_two = Button(my_frame, text='button2', command=button_two_function)
button_two.grid(row=0, column=1)
my_window.mainloop()
If I've understood corectly, you are interested in sth. like this:
from tkinter import *
root = Tk()
def click(a):
print(a)
Button(root, text='1', command=lambda: click('1')).pack()
Button(root, text='2', command=lambda: click('2')).pack()
root.mainloop()
What is happening is I'm not passing a full click function to a button, but a so called lambda function, which is essentially a one-line function. Example: if I did p = lambda: print('Hi') then each time I do p() I would see a little Hi pop up. Also, if I did k = lambda a,b: a*b then k(4,5) would return "20". More info about lambdas here.
Hope that's helpful!
def function(a,b):
print("a is :",a)
print("b is :",b)
function(10,20)
You can definitely do this without globals. You could extend the tk.button class to hold a variable like self.status = pressed.
There are a few ways you can go about this with classes. You could create either one or two classes. Maybe even have child classes for each button.
But you can just dump both your functions in one class and pass self as its first argument.
Whenever I feel the need for a global variable, I usually make a class.

Change the colour of a tkinter button generated in a loop

In python tkinter, I've got a program that generates multiple buttons with a default fg of red
from tkinter import *
root = Tk()
def dothis(i):
print(i)
button.config(fg='green')
for i in range(5):
button = Button(root, width=30, text="button{}".format(i), command=lambda i=i: dothis(i))
button.config(fg='red')
button.pack()
This creates this window:
In this program, I have attempted to make it so that once the button is pressed, the colour of the text (fg) turns green. Instead, when dothis(i) is called, it changes the colour of the last button generated to green. This is not what I want.
To summarise, when I click button3, I want to see this:
But instead, I see this (the last generated button is modified, not the one I want):
How can I work around this, while still keeping the buttons generated in a loop?
Note: The buttons must also be modifiable after changing the colour e.g. Once changed to green, it can be turned back to red.
You got the correct lambda expression, but the parameter you passed isn't related to the buttons you created. You should pass the Button widget as a parameter instead:
from tkinter import *
root = Tk()
def dothis(button):
button.config(fg='green')
for i in range(5):
button = Button(root, width=30, text="button{}".format(i))
button.config(fg='red', command=lambda i=button: dothis(i))
button.pack()
root.mainloop()
To achieve toggling between red and green, you can use ternary operator:
def dothis(button):
button.config(fg='green' if button["foreground"]=="red" else "red")
If you insist on all buttons except the last one being anonymous, and using command instead of binding events, you can use partials:
from tkinter import *
from functools import partial
root = Tk()
button_callbacks = {}
def on_click(button):
button.config(fg="green")
for i in range(5):
button = Button(root, width=30, text=f"button{i}", fg="red")
callback_name = f"on_click_{i}"
button_callbacks.update({callback_name: partial(on_click, button=button)})
button.config(command=button_callbacks[callback_name])
button.pack()
Using event binding would be a bit more straight-forward, but the behavior is not exactly the same as triggering a callback using command. Here's what that might look like:
from tkinter import *
root = Tk()
def on_click(event):
button = event.widget
button.config(fg="green")
for i in range(5):
button = Button(root, width=30, text=f"button{i}", fg="red")
button.bind("<Button-1>", on_click)
button.pack()

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, num=button[6:]: 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()

Tkinter - How would I go about resetting list of objects?

Yesterday I asked this question Creating elements by loop Tkinter to find out how to dynamically create some bullet points. Now I'm looking to add a clear button so when pressed, will reset the entire form. I have tried setting the list back to [] but it didn't work.
edit - So basically when I press reset I'd like it to look exactly like it did when the form was loaded.
The buttons are removed with the destroy method:
for button in self.button:
button.destroy()
import Tkinter as tk
class ButtonBlock(object):
def __init__(self, master):
self.master = master
self.button = []
self.button_val = tk.IntVar()
entry = tk.Entry()
entry.grid(row=0, column=0)
entry.bind('<Return>', self.onEnter)
entry.focus()
clear_button = tk.Button(master, text='Clear', command=self.onClear)
clear_button.grid(row=0, column=1)
def onClear(self):
for button in self.button:
button.destroy()
def onEnter(self, event):
entry = event.widget
num = int(entry.get())
self.onClear()
for i in range(1, num+1):
self.button.append(tk.Radiobutton(
self.master, text=str(i), variable=self.button_val, value=i,
command=self.onSelect))
self.button[-1].grid(sticky='WENS', row=i, column=0, padx=1, pady=1)
def onSelect(self):
print(self.button_val.get())
if __name__ == '__main__':
root = tk.Tk()
ButtonBlock(root)
root.mainloop()
Setting the list back (i.e. using self.button = []) just clears the data stored in the button variable. That action alone is not connected to the user interface (UI). You have to explicitly remove the widget objects which were created (by the onEnter method).
So the clearing feature you are looking for should be feasible by extending the answer from your previous question. Add an onClear method to the ButtonBlock class so that when your "Clear" control (i.e. using a button widget) is selected its callback function calls ButtonBlock.onClear(), similar to how your Entry widget invokes the onEnter method.
EDIT: See unutbu's answer to this question. When selected, the clear_button control calls ButtonBlock.onClear(). The for loop in onClear gets a reference to each button ojbect from the button list and calls the object's destroy method, which removes it from the UI.

Categories