Refering to a button then config it - python

Okay, the problem Im having is that I do not understand how to refer to that particular button that was pressed. Im doing a game, if the use clicks a button I wanna know which X and Y coordinate it had. So, if you have a grid of buttons and you click one I want the coordinates and then I will change that button's color. :)
PROBLEM: Knowing which button was pressed in a grid.
Thanks in advance!
def matris():
for i in range(5):
newButton = Button(app, width = 4, height = 2, bg = "blue",command = lambda i=i: function(i))
newButton.grid(row = i, column = 0)
for i in range(5):
newButton = Button(app, width = 4, height = 2, bg = "blue",command = lambda i=i + 5: function(i))
newButton.grid(row = i, column = 1)
for i in range(5):
newButton = Button(app, width = 4, height = 2, bg = "blue",command = lambda i=i + 10: function(i))
newButton.grid(row = i, column = 2)
for i in range(5):
newButton = Button(app, width = 4, height = 2, bg = "blue",command = lambda i=i + 15: function(i))
newButton.grid(row = i, column = 3)
for i in range(5):
newButton = Button(app, width = 4, height = 2, bg = "blue",command = lambda i=i + 20: function(i))
newButton.grid(row = i, column = 4)
def function(i):
if button 23 was clicked.changeColor to e.g "blue"

After reading all the comments and edits, it looks like what you really want to know is "which button was clicked?" when using the same command for multiple buttons.
The easiest way to do that is to pass some sort of unique identifier to the command associated with the widget. The most straight-forward way is to pass a reference to the widget itself, though that requires a two step process.
For example:
this_button = Button(...)
this_button.configure(command=lambda button=this_button: do_something(button))
def do_something(button):
print "you clicked this button:", button
You can also use functools.partial to get the same result if you find lambda hard to wrap your head around:
this_button.configure(command=functools.partial(do_something, this_button)
If you prefer to create your button in a single step instead of two, you need some way to identify it. For me, the easiest way is with a dictionary. For example, if you're creating rows and columns of buttons, you could do this:
button = {}
for r in range(10):
for c in range(10):
button[r,c] = Button(..., command=lambda row=r, column=c: do_something(row, ccolumn))
def do_something(row, column):
print "you clicked button", button[row,column]

You need to keep a reference to each button if you want to modify that button later. Since your numbering scheme is sequential you can use a list:
newButton = Button(...)
buttons.append(newButton)
...
def function(i):
widget = buttons[i-1] # -1, because list indexes are zero-based
if i == 23:
widget.configure(background="blue")
If you're creating a grid of buttons and the buttons are otherwise identical, you might want to consider a simpler structure such as:
for row in range(5):
for column in range(5):
...
It becomes instantly clear that you're creating five rows of five columns, whereas with your original code it takes several seconds of study to come to the same conclusion.

Instead of using the command argument, use the bind method to set a callback for <Button-1>. When Tkinter calls your callback, it will pass in an event object, which contains the widget that raised the event.
from Tkinter import *
def buttonClicked(e):
e.widget["bg"] = "red"
root = Tk()
for x in range(5):
for y in range(5):
newButton = Button(root, width=10, height=2, bg="blue")
newButton.bind("<Button-1>", buttonClicked)
newButton.grid(row=y, column=x)
root.mainloop()
Edit: suppose you want to treat buttons differently depending upon their position in the grid. The simplest way to do this is to use a global dictionary to associate each button with its coordinate.
from Tkinter import *
coords = {}
def buttonClicked(e):
x,y = coords[e.widget]
print "{},{} clicked".format(x,y)
if x == 4 and y == 3:
e.widget["bg"] = "red"
root = Tk()
for x in range(5):
for y in range(5):
newButton = Button(root, width=10, height=2, bg="blue")
newButton.bind("<Button-1>", buttonClicked)
newButton.grid(row=y, column=x)
coords[newButton] = (x,y)
root.mainloop()
In general, however, having a variable at the global scope isn't a good idea. It may be worth the effort to group all of your grid-based code into a single class, so none of its details leak out to the rest of the program.
from Tkinter import *
class ButtonGrid:
def __init__(self, root):
self.coords = {}
for x in range(5):
for y in range(5):
newButton = Button(root, width=10, height=2, bg="blue")
newButton.bind("<Button-1>", self.buttonClicked)
newButton.grid(row=y, column=x)
self.coords[newButton] = (x,y)
def buttonClicked(self, e):
x,y = self.coords[e.widget]
print "{},{} clicked".format(x,y)
if x == 4 and y == 3:
e.widget["bg"] = "red"
root = Tk()
b = ButtonGrid(root)
root.mainloop()
If you prefer functional over object-oriented, here's an alternative. Use the command option as you did in your original code, but use the functools.partial function to specify ahead of time which variables ought to be passed to the function.
from Tkinter import *
import functools
def buttonClicked(widget, x, y):
print "{},{} clicked".format(x,y)
if x == 4 and y == 3:
widget["bg"] = "red"
root = Tk()
for x in range(5):
for y in range(5):
newButton = Button(root, width=10, height=2, bg="blue")
newButton["command"] = functools.partial(buttonClicked, newButton, x, y)
newButton.grid(row=y, column=x)
root.mainloop()

Related

Using lambda function in command of Button widget in tkinter [duplicate]

This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 2 years ago.
I'm trying to create simple GUI like this:
three set of label, entry, and READ button, one set for one row
when READ button is pressed, the value of entry will be displayed on label.
But all Read button only read from the last entry and displayed on last label.
Here is my script:
import tkinter as tk
main = tk.Tk()
label = [None]*3
entry = [None]*3
for j in range(3):
label[j] = tk.StringVar()
tk.Label(main, textvariable = label[j], relief = 'raised', width = 7).place(x = 5, y = 40+30*j)
entry[j] = tk.Entry(main, width=8)
entry[j].place(x=80, y=40 + 30 * j)
tk.Button(main, text="READ", pady=0, padx=10, command= lambda: label[j].set(entry[j].get())).place(x=150, y=40 + 30 * j)
main.mainloop()
The problem with the code you sent is that the value of j is changing with the loop, so as the loop ends, all of your buttons and lables take the value of j as 3 (thats because when your loop ends, j has the value "3") so that means all of your lables and buttons are using the last label.
An easy fix would be to manually set label[j] and entry[j] to some other variable, then apply the command.
Something like this :
lambda x=label[j], y=entry[j]: x.set(y.get())
Here I first set label[j] to x and entry[j] to y and then change the values inside lambda.
import tkinter as tk
main = tk.Tk()
label = [None]*3
entry = [None]*3
read = [None]*3
for j in range(3):
label[j] = tk.StringVar()
tk.Label(main, textvariable = label[j], relief = 'raised', width = 7).place(x = 5, y = 40+30*j)
entry[j] = tk.Entry(main, width=8)
entry[j].place(x=80, y=40 + 30 * j)
read[j] = tk.Button(main, text="READ", pady=0, padx=10, command= lambda x=label[j], y=entry[j]: x.set(y.get()))
read[j].place(x=150, y=40 + 30 * j)
main.mainloop()

Tkinter : How to pass a button's text as an argument to a function when button is clicked

I have the following code that generates a random button grid of 5x5 dimensions :
import tkinter as tk
from tkinter import *
from tkinter import messagebox
import random
def numberClick(num):
messagebox.showinfo('Message', 'You clicked the '+str(num)+' button!')
root = Tk()
#root.geometry("200x200")
w = Label(root, text='Welcome to Bingo!')
linear_array = [i for i in range(1,26)]
random_array = []
for i in range(1,26):
temp = random.choice(linear_array)
linear_array.remove(temp)
random_array.append(temp)
for i in range(25):
num = random.choice(random_array)
#tk.Label(root,text=num).grid(row=i//5, column=i%5)
redbutton = Button(root, text = num, fg ='red',height = 3, width = 5,command=lambda: numberClick(num))
redbutton.grid(row=i//5, column=i%5)
root.mainloop()
I have implemented the command function and passed a parameter with lambda as shown :
redbutton = Button(root, text = num, fg ='red',height = 3, width = 5,command=lambda: numberClick(num))
Now when I click a button , the function call should print the text value assigned with it. Instead it just prints the same value , that is the last assigned value in num variable :
Output when i clicked on button 20
Any workarounds?? TIA.
Just change your button to:
redbutton = Button(root, text = num, fg ='red',height = 3, width = 5,command=lambda num=num: numberClick(num))
This should fix the issue, this will store the value of num in lambda rather than just looping and using the last value of num.
I was just about to point out the same thing a Cool Cloud, but I'd also like to add that you are randomizing twice such that you might get duplicate numbers.
The first for loop randomizes the numbers 1-25 in random_array, but then in the second loop you randomly select one element from that list without removing it when you initialize num. I'd write the second loop as:
for i in range(25):
num = random_array[i]
redbutton = Button(root, text = num, fg ='red',height = 3, width = 5, command=lambda n=num: numberClick(n))
redbutton.grid(row=i//5, column=i%5)

How to 'fix' selected item in tkinter.Listbox?

Here's is an extract from my code:
import tkinter as tk
def evaluate(event):
print(list_box.curselection())
root = tk.Tk()
var = tk.StringVar()
var.set(0)
entry = tk.Entry(root, textvariable = var)
entry.place(x = 150, y = 0, width = 20)
entry.bind("<Return>", evaluate)
list_box = tk.Listbox(root, selectmode = 'single')
list_box.place(x = 0, y = 0)
lst = [1, 2, 3]
for elem in lst:
list_box.insert('end', elem)
list_box.selection_set(first = 0)
list_box.bind("<<ListboxSelect>>", evaluate)
root.mainloop()
The problem is that I want to 'fix' somehow the last selected value in the tkinter.Listbox. I mean, if in the window we type something to the entry, then in some cases (probably, it depends on how you click the entry box) the value chosen in the list will be lost. Is it possible to save, for example, the last selected value?
I'm new to Python and, in particular, to tkinter package, so any help would be appreciated.
the problem is as soon as you select something else like the input of the entry, your listbox lose the shown selection. I removed the default binding for double click, but if you still want to use it to write input, I can't remove the single click (B1-Motion dosent works either). At least I dont know how. I recommand to use the entry just as display or vise versa.
import tkinter as tk
def evaluate(event):
print(list_box.curselection())
root = tk.Tk()
var = tk.StringVar()
var.set(0)
entry = tk.Entry(root, textvariable = var)
entry.place(x = 150, y = 0, width = 20)
entry.bind("<Return>", evaluate)
entry.bind('<Double-Button-1>', lambda e: "break")
list_box = tk.Listbox(root, selectmode = 'single')
list_box.place(x = 0, y = 0)
lst = [1, 2, 3]
for elem in lst:
list_box.insert('end', elem)
list_box.selection_set(first = 0)
list_box.bind("<<ListboxSelect>>", evaluate)
root.mainloop()

How to make a grid of buttons giving them a name (even if I create them with a "for")

I recently saw a question on "how to make a grid of buttons whose size changes automatically". I found a very interesting example of code there, however, the proposed method created the buttons in a "for", which did not allow them to be set to a specific parameter. Here is this code:
frame = Frame(root)
Grid.rowconfigure(root, 0, weight = 1)
Grid.columnconfigure(root, 0, weight = 1)
frame.grid(row = 0, column = 0, sticky = N + S + E + W)
grid = Frame(frame)
grid.grid(sticky = N + S + E + W, column = 0, row = 7, columnspan = 2)
Grid.rowconfigure(frame, 7, weight = 1)
Grid.columnconfigure(frame, 0, weight = 1)
for x in range(10):
for y in range(5):
btn = Button(frame)
btn.grid(column = x, row = y, sticky = N + S + E + W)
for x in range(10):
Grid.columnconfigure(frame, x, weight = 1)
for y in range(5):
Grid.rowconfigure(frame, y, weight = 1)
Could you tell me how to make each button different?
One issue I see here is you import tkinter as tk but do not use the prefix tk. when trying to set up your frames or buttons. This leads me to believe you may also be doing from tkinter import * and this is really bad idea especially when you write grid = Frame(root) as you are overwriting the grid() method one line before you actually try to use grid().
By using a list of buttons we can reference the index where the button is stored and do something with it.
Se below example and let me know if you have any questions:
import tkinter as tk
def some_function(ndex):
print(button_list[ndex]['text'])
button_list[ndex].config(text='', background='black')
print(button_list[ndex]['text'])
root = tk.Tk()
root.geometry('300x200')
button_list = []
for x in range(15):
root.columnconfigure(x, weight=1)
for y in range(17):
button_list.append(tk.Button(root))
count = len(button_list)
button_list[-1].config(text='{}'.format(count), command=lambda ndex=count-1: some_function(ndex))
button_list[-1].grid(column=x, row=y, sticky='nsew')
if x == 0:
root.rowconfigure(y, weight=1)
root.mainloop()
For the fun of it and the approaching holiday here is a Jack-o'-lantern made from the code :D
To answer you question in the comments see below code:
import tkinter as tk
def some_function(value):
print(value)
root = tk.Tk()
button_values = [['A', 'B', 'C'], ['=', '+', '-']]
button_list = []
for ndex, sub_list in enumerate(button_values):
root.columnconfigure(ndex, weight=1)
for sub_ndex, value in enumerate(sub_list):
button_list.append(tk.Button(root))
count = len(button_list)
button_list[-1].config(text=value, command=lambda x=value: some_function(x))
button_list[-1].grid(column=ndex, row=sub_ndex, sticky='nsew')
root.mainloop()
Results:
Console after pressing each button:
A
B
C
=
+
-

Change tkinter entry widget multiple times after pressing a button

I have a tkinter Entry widget and when a user presses a button the contents update:
from tkinter import *
window = Tk()
def abcdef(num):
ent.config(state=NORMAL)
ent.delete(0, 'end')
ent.insert(0, num)
ent.config(state = "readonly")
print(num) #Just to check the code is being run
def changeEntry():
for j in range(3):
ent.after(1000, abcdef(j))
ent = Entry(widow, text="", state = "readonly", readonlybackground="white", font = "20")
ent.grid(row = 0, column = 0, columnspan = 3, sticky = "E")
btn = Button(window, text="Button", command=changeEntry)
btn.grid(row = 1, column = 0, sticky = "NESW", pady = 10, padx = 10)
window.mainloop()
When I press the button the window freezes for 3 seconds and then just displays the final number. How can I make it so when the user presses the button, the entry changes every second instead of just freezing for 3 seconds and only displaying the final one?
Thanks in advance
You have two problems with that .after call. The .after method tells Tkinter to call the function you pass it after the time interval has passed. But you're telling Tkinter to do 3 things after 1000 milliseconds have passed, so they'll all happen on top of each other. So you need to stagger the delays.
Secondly, you need to give .after a function to call when its time to call it. But your code calls the function and gives .after the return value of your function. We can fix that by wrapping the function call inside another function. A convenient way to do that is using lambda, giving the lambda a default argument it can pass to abcdef
import tkinter as tk
window = tk.Tk()
def abcdef(num):
ent.config(state=tk.NORMAL)
ent.delete(0, 'end')
ent.insert(0, num)
ent.config(state = "readonly")
print(num) #Just to check the code is being run
def changeEntry():
for j in range(3):
ent.after(1000 * j, lambda num=j: abcdef(num))
ent = tk.Entry(window, text="", state = "readonly", readonlybackground="white", font = "20")
ent.grid(row = 0, column = 0, columnspan = 3, sticky = "E")
btn = tk.Button(window, text="Button", command=changeEntry)
btn.grid(row = 1, column = 0, sticky = "NESW", pady = 10, padx = 10)
window.mainloop()
I've also replaced that "star" import with the neater import tkinter as tk. That makes it obvious which names come from Tkinter and which names are local to your program.
Bryan Oakley points out that we don't need that lambda, we can pass in arguments after the function name. See the Basic Widget Methods in the Tkinter docs for details. So we can re-write changeEntry like this:
def changeEntry():
for j in range(3):
ent.after(1000 * j, abcdef, j)
Thanks, Bryan!

Categories

Resources