How to get widget name in event? - python

from tkinter import *
main = Tk()
def flipper(event):
# I'd like to do this:
#if widgetname == switcher:
#do stuff
#if widgetname == switcher1:
#do stuff
return
switcher = Label(main, bg='white', text="click here", font="-weight bold")
switcher.grid()
switcher.bind("<Button-1>", flipper)
switcher1 = Label(main, bg='white', text="click here", font="-weight bold")
switcher1.grid()
switcher1.bind("<Button-1>", flipper)
switcher2 = Label(main, bg='white', text="click here", font="-weight bold")
switcher2.grid()
switcher2.bind("<Button-1>", flipper)
switcher3 = Label(main, bg='white', text="click here", font="-weight bold")
switcher3.grid()
switcher3.bind("<Button-1>", flipper)
switcher4 = Label(main, bg='white', text="click here", font="-weight bold")
switcher4.grid()
switcher4.bind("<Button-1>", flipper)
switcher5 = Label(main, bg='white', text="click here", font="-weight bold")
switcher5.grid()
switcher5.bind("<Button-1>", flipper)
main.mainloop()
In my event function I'd like to do different things based on the label that is clicked. What im stumped on is that I can only get the identifier number of the widget that is clicked, not the name. If I could get the identifier of all my widgets then I could do:
def flipper(event):
if event.widget == switcher.identifier():
do stuff
but I can't find how to get the id of a specified widget either...
How can I get the name of a widget by its identifier (event.widget())?
Or how can I get the identifier of a specified widget name?
If neither are possible, then I'd have to make a different function and bind for each label which is a lot of work that hopefully is not necessary.
Edit:
from tkinter import *
main = Tk()
def flipper(event, switch):
if switch.widget == 's1':
print("got it")
switcher = Label(main, bg='white', text="click here", font="-weight bold")
switcher.grid()
switcher.bind("<Button-1>", flipper)
switcher.widget = 's1'
main.mainloop()

You can't get the variable name that the widget is assigned to, that would be relatively useless. A widget could be assigned to more than one variable, or none at all.
Getting the label text
You have access to the actual widget, and you can use that to get the text that is on the label. Your example shows that all labels are the same, so this might not be useful to you:
def flipper(event):
print("label text:", event.widget.cget("text"))
Using a custom widget name
You can also give a widget a name. You can't get back precisely the name, but you can come very close. For example, if you create a label like this:
switcher = Label(main, name="switcher", bg='white', text="click here", font="-weight bold")
You can get the string representation of the widget by splitting on "." and taking the last value:
def flipper(event):
print("widget name:", str(event.widget).split(".")[-1])
Passing a name via the binding
Finally, you can set up your bindings such that the name is sent to the function:
switcher.bind("<Button-1>", lambda event: flipper(event, "switcher"))
switcher1.bind("<Button-1>", lambda event: flipper(event, "switcher1"))

You can use event.widget to get standard parameters from clicked widget
example:
import tkinter as tk
def callback(event):
print(event.widget['text'])
main = tk.Tk()
switcher = tk.Label(main, text="click here")
switcher.grid()
switcher.bind("<Button-1>", callback)
main.mainloop()
You can assign own variables to widgets
switcher.extra = "Hello"
and then get it
event.widget.extra
example:
import tkinter as tk
def callback(event):
print(event.widget['text'])
print(event.widget.extra)
main = tk.Tk()
switcher = tk.Label(main, text="click here")
switcher.grid()
switcher.bind("<Button-1>", callback)
switcher.extra = "Hello"
main.mainloop()
You can use lambda to bind function with arguments
bind("<Button-1>", lambda event:callback(event, "Hello"))
example:
import tkinter as tk
def callback(event, extra):
print(event.widget['text'])
print(extra)
main = tk.Tk()
switcher = tk.Label(main, text="click here")
switcher.grid()
switcher.bind("<Button-1>", lambda event:callback(event, "Hello"))
main.mainloop()

I had the same issue I found easy way was to use bind method.
apparent name property is private but can be accessed via _name
This is useful if you plan to generate widgets dynamically at runtime
# Import Module
from tkinter import *
# create root window
root = Tk()
# root window title and dimension
root.title("Welcome to Test window")
# Set geometry (widthxheight)
root.geometry('350x200')
#adding a label to the root window
lbl = Label(root, text = "Press a button")
lbl.grid()
#define mouse up event
def mous_up(ev:Event):
#get calling widget from event
sender:Button = ev.widget
#set label text
lbl.configure(text = sender._name + " up")
#read foreground color from button
#If red make green, else make red
if sender.cget('fg') == "red":
#normal color
sender.configure(fg='lime')
#mouse over color
sender.configure(activeforeground='green')
else:
#normal color
sender.configure(fg="red")
#mouse over color
sender.configure(activeforeground='darkred')
#define mouse down event
def mous_down(ev:Event):
lbl.configure(text = str(ev.widget._name) + " down")
# button widget with red color text
# inside
btn = Button(root, text = "Click me" ,
fg = "red",name = "button-A")
#bind mouse up and mouse down events
btn.bind('<ButtonRelease-1>',mous_up)
btn.bind('<Button-1>',mous_down)
# set Button grid
btn.grid(column=0, row=1)
#Create another button
btn = Button(root, text = "Click me2" ,
fg = "red",name="button2")
#bind mouse up and mouse down events
btn.bind('<ButtonRelease-1>',mous_up)
btn.bind('<Button-1>',mous_down)
#absolute placement of button instead of
#using grid system
btn.place(x=50,y=100)
# all widgets will be here
# Execute Tkinter
root.mainloop()

Quick and dirty - you could have the function check a switcher attribute.
def flipper(event, switch):
if switch.widget == 's1':
do_stuff
return stuff
if switch.widget == 's2':
do_stuff
return stuff
switcher1.widget = 's1'
switcher2.widget = 's2'

I know this is an old post, but I had the same problem and I thought I should share a solution in case anyone is interested. You can give your widget a name by creating a subclass of the widget. E.g. "Button" is a widget. You can make a child widget "MyButton" which inherits from button and then add an instance variable to it (e.g. name, uniqueID etc.)
Here is a code snippet
class MyButton(Button):
def __init__(self, master = None, textVal = "", wName = ""):
Button.__init__(self, master, text = textVal)
self.widgetID = wName #unique identifier for each button.
When you want to create a new button widget, use
b = MyButton(.....),
instead of
b = Button(.......)
This way, you have all the functionality of a button, plus the unique identifier.

Related

Tkinter - Python, how do I cause a button click to assign a value to a variable?

Using Tkinter and Python. Already created a window for the buttons to be placed on. I want there to be four buttons to appear, and I want to be able to click one of the four buttons, and be able for it to set the selection variable = "whatever I clicked", so that I can then use this variable later to call an API. When I run the program and click on the "General knowledge" button and print the selection, it does correctly print "General knowledge", but then when I try to return this selection variable it just doesn't work and I don't know why.
def select1():
selection = "General Knowledge"
print(selection)
def select2():
selection = "Science"
def select3():
selection = "Entertainment"
def select4():
selection = "Miscellaneous"
button1 = tk.Button(text = "General Knowledge", command = select1)
button1.place(x=100, y=100)
button2 = tk.Button(text = "Science", command = select2)
button2.place(x=100, y=140)
button3 = tk.Button(text = "Entertainment", command = select3)
button3.place(x=100, y=180)
button4 = tk.Button(text = "Miscellaneous", command = select4)
button4.place(x=100, y=220)
There are several ways to accomplish your goal.
One way is to write a single function that will take a value to assign to your variable. This way you can have as many buttons as you like and only a single function.
Not if you are using functions you have to either pass the variable to the function or let the function know it is in the global namespace.
import tkinter as tk
root = tk.Tk()
selection = ''
def assign_value(value):
global selection
selection = value
lbl["text"] = value
print(selection)
lbl = tk.Label(root, text='Selection Goes Here')
lbl.grid(row=0, column=0)
tk.Button(text="General Knowledge", command=lambda: assign_value("General Knowledge")).grid(row=1, column=0)
tk.Button(text="Science", command=lambda: assign_value("Science")).grid(row=2, column=0)
tk.Button(text="Entertainment", command=lambda: assign_value("Entertainment")).grid(row=3, column=0)
tk.Button(text="Miscellaneous", command=lambda: assign_value("Miscellaneous")).grid(row=4, column=0)
root.mainloop()
Or you can assign the value directly from the button.
import tkinter as tk
root = tk.Tk()
selection = tk.StringVar()
selection.set('Selection Goes Here')
lbl = tk.Label(root, textvariable=selection)
lbl.grid(row=0, column=0)
tk.Button(text="General Knowledge", command=lambda: selection.set("General Knowledge")).grid(row=1, column=0)
tk.Button(text="Science", command=lambda: selection.set("Science")).grid(row=2, column=0)
tk.Button(text="Entertainment", command=lambda: selection.set("Entertainment")).grid(row=3, column=0)
tk.Button(text="Miscellaneous", command=lambda: selection.set("Miscellaneous")).grid(row=4, column=0)
root.mainloop()
I am sure if I spent more time on this I could think up something else but the idea is basically write your code in a more DRY (Don't Repeat Yourself) fashion and make sure you are assigning the value to the variable in the global namespace or else it will not work as you expect.

Close two windows with one click in tkinter

I am trying to get a button in one function to close the window generated from another function. Here is a shortened version of the code. Basically I want the top window generated from add_drink to be closed when the close_button is clicked at the save_drink stage. How can I do this?
def save_drink(added_drink):
drinks_list = []
newtop = Toplevel(root)
newtop.geometry("200x200")
newtop.title("Drink Added")
label = Label(newtop, text= "{} Added".format((added_drink.get())), font=('Mistral 10')).pack()
close_button = Button(newtop, text="Close", command=newtop.destroy)
close_button.pack()
drinks_list.append(added_drink.get())
def add_drink():
top = Toplevel(root)
top.geometry("750x250")
top.title("Record Drink")
label = Label(top, text= "What drink did you have?", font=('Mistral 18')).pack()
added_drink = Entry(top, font=6)
added_drink.pack()
added_drink_button = Button(top, text='Add Drink', font=3,
command=lambda: save_drink(added_drink)).pack()
You could try passing top from add_drink as a parameter to save_drink
added_drink_button = Button(
top,
text='Add Drink',
font=3,
command=lambda top=top: save_drink(added_drink, top) # add top to the lambda
).pack()
then modify save_drink to accept the top parameter and dispose of it accordingly
in a new function that handles closing both Toplevel widgets
def save_drink(added_drink, top): # add 'top' here
drinks_list = []
newtop = Toplevel(root)
newtop.geometry("200x200")
newtop.title("Drink Added")
label = Label(
newtop,
# I suspect there's some extra parentheses around 'format'
text= "{} Added".format((added_drink.get())), font=('Mistral 10')
).pack()
close_button = Button(
newtop,
text="Close",
# call the 'close' function using a lambda to pass args
command=lambda t=top, n=newtop: close(t, n)
)
close_button.pack()
drinks_list.append(added_drink.get())
This function will close both windows when called by close_button
def close(top, newtop): # pass both windows as parameters
top.destroy()
newtop.destroy()
This should work, though I suspect it might be easier to wrap both the add_drink and save_drink functions in a simple class so they can share information more easily.

Call variables in function by adding to variable number?

I'm Creating a gui in tkinter and have buttons named btn1 btn2 btn3 etc, what i want the button to do on click is disable the button clicked and enable the next button in order. I can write out 6 seperate functions but that seems to defeat the point of a function.
if (btn1['state'] == tk.NORMAL):
btn1.config(state=tk.DISABLED),
btn2.config(state=tk.NORMAL)
else: print ('already clicked')
this is what i have now, but i want it to look more like btn #+1 (state=DISABLED)
You can put the buttons in a list, and then iterate over the list.
Here's a bit of a contrived example:
import tkinter as tk
root = tk.Tk()
def click(button_number):
button = buttons[button_number]
button.configure(state="disabled")
if button == buttons[-1]:
# user clicked the last button
label.configure(text="BOOM!")
else:
next_button = buttons[button_number+1]
next_button.configure(state="normal")
next_button.focus_set()
label = tk.Label(root, text="")
label.pack(side="bottom", fill="x")
buttons = []
for i in range(10):
state = "normal" if i == 0 else "disabled"
button = tk.Button(root, text=i+1, state=state, width=4,
command=lambda button_number=i: click(button_number))
button.pack(side="left")
buttons.append(button)
buttons[0].focus_set()
root.mainloop()

Change the color of 2 buttons in case of clicking on one of them

Using this code:
import tkinter as tk
root = tk.Tk()
btn = tk.Button(root,text="click me",activebackground="red")
btn.grid()
root.mainloop()
I know how to change the background color of a button in case it is pressed (however it's temporary and when I pick the mouse click, the color changes to its default color.)
Suppose I have two buttons, one of them is the wrong answer and the other one is the right answer. Is it possible to change the code so that when the user clicks on the wrong button, its background color changes to red and at the same time the background color of the other button changes to green (I want these changes be permanent)
You can use the command to call a function that sets the background color of the two buttons permanently (via configuring bg property).
The following code changes the color of both buttons when the wrong button is pressed:
import tkinter as tk
def change_color(btn1, btn2):
btn1.configure(bg="green")
btn2.configure(bg="red")
root = tk.Tk()
btn = tk.Button(root,text="correct button")
btn.pack()
btn2 = tk.Button(root,text="wrong button", command=lambda: change_color(btn, btn2))
btn2.pack()
root.mainloop()
A button without a callback is pretty useless. It doesn’t do anything when you press the button.
A function or method that is called when the button is pressed is called the command. The callback can be a function, bound method, or any other callable Python object. If this option is not used, nothing will happen when the user presses the button.
You can use the event of the button being clicked and add a configuration/command to the button. This command will tell the program what to do, when the button is clicked.
As I have done below:
import tkinter as tk
def btn_click():
btn.configure(bg='red')
btn2.configure(bg='green')
root = tk.Tk()
btn = tk.Button(root, text="click me", command=btn_click)
btn.grid()
btn2 = tk.Button(root, text="click me")
btn2.grid()
root.mainloop()
Hope this was helpful!
I assume that the button that will turn red or green will change depending on the question. I wrote a simple little question and answer app as an example of how to dynamically change the buttons. The app is ugly (because it is a quick example), but it works. The doc strings and comments in the code explain how I change the buttons dynamically.
#python 3.8+
from tkinter import Tk, Frame, Button, Entry, StringVar
class Questions(Frame):
def __init__(self, master, questions, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
self.out = StringVar()
self.correct = 0
self.current = 0
self.questions = questions
self.q = Entry(self, width=36, font='calibri 14 bold', textvariable=self.out)
self.q.grid(row=0, column=0, columnspan=3)
self.def_btn = dict(
font = 'calibri 14',
background = 'yellow',
foreground = 'black',
activebackground = 'yellow',
)
'''
we give command a lambda so we can pass an id to our answer method
this "id" will be used to perform some simple maths to change the buttons dynamically
'''
self.answer_1 = Button(self, command=lambda id=1: self.answer(id), **self.def_btn)
self.answer_1.grid(row=1, column=0)
self.answer_2 = Button(self, command=lambda id=2: self.answer(id), **self.def_btn)
self.answer_2.grid(row=1, column=1)
self.next = Button(self, text="next", font='calibri 14', command=self.next_question)
self.next.grid(row=1, column=2,)
for i in range(3):
if i < 2:
self.grid_rowconfigure(i, weight=1)
self.grid_columnconfigure(i, weight=1)
#kick off the first question
self.next.invoke()
def init_question(self, q, a1, a2, id):
self.out.set(q)
self.answer_1.config(text=a1, **self.def_btn)
self.answer_2.config(text=a2, **self.def_btn)
self.correct = id
self.next['state'] = 'disabled'
def answer(self, id):
'''
self.correct can only be 1 or 2 so
if 1 ~ abs(1-3) is 2
if 2 ~ abs(2-3) is 1
and in that we can change both buttons dynamically
we target the __dict__ so we can concoct a key for the desired button
'''
self.__dict__[f'answer_{self.correct}']['bg'] = 'green'
self.__dict__[f'answer_{abs(self.correct-3)}']['bg'] = 'red'
self.out.set(f'That is {"in" if id != self.correct else ""}correct!')
self.next['state'] = 'normal'
def next_question(self):
if len(self.questions) > self.current:
self.init_question(**self.questions[self.current])
self.current += 1
class App(Tk):
WIDTH = 400
HEIGHT = 200
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
Questions(self, [
# question, both answers, correct answer id
dict(q='Are you homeless?', a1='true', a2='false', id=1),
dict(q='Is your name Unghanda Magdebudhu?', a1='true', a2='false', id=1),
dict(q='Can you spell "CAT"?', a1='true', a2='false', id=2),
]).grid(row=0, column=0, sticky='nswe')
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
if __name__ == '__main__':
app = App()
app.title("My Application")
app.geometry(f'{App.WIDTH}x{App.HEIGHT}')
app.minsize(App.WIDTH, App.HEIGHT)
app.mainloop()

Python Tkinter how can I place background text in a textbox?

I am trying to place a greyed out background text inside a textbox, that disappears when someone begins to type. I have tried overlaying a label onto the textbox, but I could not get it to work. Here is some code I am running for the text box
root = tk.Tk()
S = tk.Scrollbar(root)
T = tk.Text(root, height=70, width=50)
S.pack(side=tk.RIGHT, fill=tk.Y)
T.pack(side=tk.LEFT, fill=tk.Y)
S.config(command=T.yview)
T.config(yscrollcommand=S.set)
root.protocol("WM_DELETE_WINDOW", stop)
tk.mainloop()
How can I put in the background text?
Using a callback function you can remove the default text and change the foreground colour
import tkinter as tk
root = tk.Tk()
e = tk.Entry(root, fg='grey')
e.insert(0, "some text")
def some_callback(event): # must include event
e.delete(0, "end")
e['foreground'] = 'black'
# e.unbind("<Button-1>")
e.unbind("<FocusIn>")
return None
# e.bind("<Button-1>", some_callback)
e.bind("<FocusIn>", some_callback)
e.pack()
root.mainloop()
You are probably talking about placeholders , tkinter does not have placeholder attribute for Text widget, but you do something similar with Entry widget . You have to do it manually, by binding an event with the Text widget.
text = tk.StringVar()
text.set('Placeholder text')
T = tk.Entry(root, height=70, width=50, textvariable = text)
def erasePlaceholder(event):
if text.get() == 'Placeholder text':
text.set('')
T.bind('<Button-1>', erasePlaceholder)
Try to ask if you face any issues.

Categories