How to know which Entry has been clicked? - python-3.x

I have a program that creates entry widgets using a for loop:
from tkinter import *
root = Tk()
entList = []
def deleteChar(event):
ent.delete(0, 'end')
ent.insert(0, '')
ent.config(fg='black')
for x in range(12):
ent = Entry(root, fg='grey60')
ent.insert(0, 'Enter Name')
ent.pack()
ent.bind('<FocusIn>', deleteChar)
entList.append(ent)
root.mainloop()
Is there any way to make the function recognize which entry has been clicked so it will delete the text in that one instead of only the last one created?

Exactly one widget in an application will have keyboard focus. You can query for which widget has the focus. In addition, the event object that is passed in has a reference to the widget that triggered the callback, which is typically what you do in an event callback.
def deleteChar(event):
event.widget.delete(0, 'end')
event.widget.insert(0, '')
event.widget.config(fg='black')

Related

Python3 Tkinter, how do I get a variable inside of a def?

I'm trying to get the variable "user_get" inside the def entered(user_input)
import tkinter as tk
from tkinter import ttk
window = tk.Tk()
#put settings in place (username input)
window.geometry('1920x1080')
window.configure(bg = 'blue')
def entered(user_input):
user_get = user_input.widget.get()
user_input.widget.delete(0, 'end')
print(user_get)
return user_get
user_input.widget.destroy()
# TextBox (input)
user_input = tk.Entry(window)
user_input.pack()
user_input.place(x = 100,y = 40)
user_input.bind("<Return>", entered)
thing = user_get
print(thing)
window.mainloop()
I have tried:
-return (I don't really understand it that well)
Here I've made it so the user_input variable is available in the global scope. The global keyword in the entered function allows that function to modify the global variable. Now, when you type into the entry and hit Return, the value of user_input is updated.
I defined an example function that will print this value whenever a button is pressed. Note that until you add text to the Entry and press Return, an empty string will be printed!
Likewise, any calls like print(user_input) made immediately before root.mainloop() will print an empty string because the value of user_input hasn't been updated yet.
import tkinter as tk
def entered(_event):
global user_input # allow this function to modify this variable
user_input = entry.get() # update the variable with the entry text
entry.delete(0, 'end') # clear entry contents
def use_value_from_entry():
"""Example function to use the value stored in 'user_input'"""
print(user_input)
window = tk.Tk()
user_input = '' # create a variable to store user input in the global scope
entry = tk.Entry(window) # create the entry widget
entry.pack() # add the widget to the UI
entry.bind('<Return>', entered) # bind the event handler
# this button runs an example function to get the current value of 'user_input'
button = tk.Button(window, text='Click Me', command=use_value_from_entry)
button.pack()
window.mainloop() # run the app
#Stockmage updated this

I want to click on tkinter entry to bind even to return three difference values to entry

I want to click on tkinter entry to change it's value to three values on each click, Present, Leave, Absent, means how event function will return difference values each time , on first click, Present, next click Absent, next click Leave, and then back Present
I will be vary thankful for your kind support :)
I can't figure out any better method. I hope it's good enough. If it's not what you are expecting, let me know in the comment (I'm not sure if I understood you correctly).
import tkinter as tk
def handle_click(event):
if entry.get() == '':
entry.delete(0, tk.END)
entry.insert(0, 'Present')
elif entry.get() == 'Present':
entry.delete(0, tk.END)
entry.insert(0, 'Leave')
elif entry.get() == 'Leave':
entry.delete(0, tk.END)
entry.insert(0, 'Absent')
elif entry.get() == 'Absent':
entry.delete(0, tk.END)
entry.insert(0, '')
print(event)
root = tk.Tk()
root.geometry('500x500')
entry = tk.Entry(root, width=50)
entry.grid(row=0, column=0, padx=5, pady=5)
entry.bind("<1>", handle_click)
root.mainloop()
You can use cycle() from itertools module and next() function to cycle the three values and bind <Button-1> event to change the content of the Entry:
import tkinter as tk
from itertools import cycle
values = cycle(('Present', 'Leave', 'Absent'))
root = tk.Tk()
e_var = tk.StringVar()
# create a Entry that can be changed via mouse click only
entry = tk.Entry(root, textvariable=e_var, state='disabled', disabledforeground='black')
entry.pack()
# bind mouse left click to update the Entry from the cycle list
entry.bind('<Button-1>', lambda e: e_var.set(next(values)))
root.mainloop()

Dynamic Form Widget interaction in python tkinter

I am trying to generate a blank GUI, with 1 menu Item.
I then use a function to generate a label, a button and an entry widget on the same form when the selection is made from the menu item.
However when I try to use the get() method to get the value of the input in the generated textbox, I get an error. I may have missed some core concept here and this may not be possible, but I would like to know. Following is my code,
from tkinter import Tk, Label, Button, Entry, Menu
def btn_clientadd():
print(txt1.get())
def addclient():
lbl1 = Label(window, text="Client Name :")
lbl1.grid(row=1,column=1,padx=7,pady=7,sticky='e')
txt1 = Entry(window)
txt1.grid(row=1, column=2)
txt1.focus()
btn = Button(window, text="Add Client", command=btn_clientadd)
btn.grid(row=2,column=2,padx=7,pady=7)
window = Tk()
window.geometry('400x200')
menu = Menu(window)
new_item1 = Menu(menu)
menu.add_cascade(label='ClientMaster', menu=new_item1)
new_item1.add_command(label='Add New Client', command=addclient)
window.config(menu=menu)
window.mainloop()
The entry txt1 is created inside a function and the reference to it is garbage collected when the function ends. One way you can get around this it to declare a StringVar() in the global scope and then associate it to the entry.
Examine the example below:
from tkinter import Tk, Label, Button, Entry, Menu, StringVar
def btn_clientadd():
print(client_string.get()) # Get contents of StringVar
def addclient():
lbl1 = Label(window, text="Client Name :")
lbl1.grid(row=1,column=1,padx=7,pady=7,sticky='e')
# Create entry and associate it with a textvariable
txt1 = Entry(window, textvariable=client_string)
txt1.grid(row=1, column=2)
txt1.focus()
btn = Button(window, text="Add Client", command=btn_clientadd)
btn.grid(row=2,column=2,padx=7,pady=7)
window = Tk()
window.geometry('400x200')
menu = Menu(window)
new_item1 = Menu(menu)
menu.add_cascade(label='ClientMaster', menu=new_item1)
new_item1.add_command(label='Add New Client', command=addclient)
window.config(menu=menu)
client_string = StringVar() # StringVar to associate with entry
window.mainloop()

Problems with Focus

I'm trying to return the focus to the first entry. If you move the focus to the next entry or the button and the you click on the button, the focus returns fine to first entry. When I try doing the same thing by using the tab key, the focus_set method fails. I've tried many different ways, but the result is always the same. Anyone knows why? And might be so kind as to showing me how to do it right? Thanks in advance.
This is what I got so far:
from tkinter import *
w = Tk()
def focus():
box1.focus_set()
def check(event):
if str(event.widget) == '.!entry2':
print('focus back to box1')
focus()
box1 = Entry(w, width=15)
box2 = Entry(w, width=15)
box1.focus_set()
box2.bind('<Tab>', check)
box1.pack()
box2.pack()
btn = Button(w, text='Box 1 Focus', command=focus)
btn.pack()
w.mainloop()
If I run your code, str(event.widget) is something like ".36580648", not ".!entry2". You can give your widget a custom name like
box2 = Entry(w, width=15, name='second')
You can then check if str(event.widget) == '.second'.
Alternatively, you can just check if event.widget == box2: which is easier and less prone to error.
If you do one of these things, you will see that 'focus back to box1' is printed, but the focus is still transferred to the button instead of the label. This is because your custom event is triggered before the default event for <Tab>, which is to move focus to the next widget. You can stop the default event handling by returning 'break' in your function.
The complete example would become:
from tkinter import *
w = Tk()
def focus():
box1.focus_set()
def check(event):
if event.widget == box2:
print('focus back to box1')
focus()
return 'break'
box1 = Entry(w, width=15)
box2 = Entry(w, width=15)
box1.focus_set()
box2.bind('<Tab>', check)
box1.pack()
box2.pack()
btn = Button(w, text='Box 1 Focus', command=focus)
btn.pack()
w.mainloop()

Passing OptionMenu into a callback (or retrieving a reference to the used widget)

I'm working on a (toplevel in a) GUI that consists of an array of 8 OptionMenus, each of them containing the same option list. Currently, Im building these widgets using a for-loop, and I save references in a dictionary. All OptionMenus link to the same (lambda) callback function.
To stay practical: the items in the option list represent a sequence of processing steps, and the user can alter the order of processes.
A change in one of the lists will result in one process being executed twice, and one process not at all. However, I want each item to occur only once. Hence, each user input should be accompanied by a second OptionMenu alteration.
For example: initial order 1-2-3 --> user changes the second process: 1-3-3, which autocorrects to: 1-3-2, where each process is again executed only once.
To my understanding, I can only get this to work if I have a reference to the OptionMenu that was just altered (from within the callback function). I was looking into passing the widget into the callback. The sample code is an attempt to implement the second suggested method, but the result is not what I would have expected.
The thing is that the OptionMenu widget seems to behave somewhat differently from other widgets. The OptionMenu does not allow for a re-defintion of the command function. No matter what input I pass along with the command function, the callback only seems to retrieve the OptionMenu selection, which is insufficient information for me to determine my process order.
Suggestions would be much apreciated!
import tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W)
self.create_widgets()
def create_widgets(self):
self.active_procs = ['proc 1','proc 2','proc 3','proc 4',
'proc 5','proc 6','proc 7','proc 8']
itemnr, widgets = dict(), dict()
for index in range(8):
name_construct = 'nr' + str(index)
itemnr[name_construct] = tk.StringVar(root)
itemnr[name_construct].set(self.active_procs[index])
widgets[name_construct] = tk.OptionMenu(self, itemnr[name_construct], *self.active_procs,
command=lambda widget=name_construct:
self.order_change(widget))
widgets[name_construct].grid(row=index+2, column=2, columnspan=2,
sticky="nwse", padx=10, pady=10)
def order_change(self,widget):
print(widget)
root = tk.Tk()
root.title("OptionMenu test")
app = Application(master=root)
root.mainloop()
The OptionMenu will pass the new value to the callback, so you don't have to do anything to get the new value. That's why your widget value isn't the value of name_construct -- the value that is passed in is overwriting the default value that you're supplying in the lambda.
To remedy this you simply need to add another argument so that you can pass the value of name_construct to the callback to go along with the value which is automatically sent.
It would look something like this:
widgets[name_construct] = tk.OptionMenu(..., command=lambda value, widget=name_construct: self.order_change(value, widget))
...
def order_change(self, value, widget):
print(value, widget)
Note: the OptionMenu isn't actually a tkinter widget. It's just a convenience function that creates a standard Menubutton with an associated Menu. It then creates one item on the menu for each option, and ties it all together with a StringVar.
You can get the exact same behavior yourself fairly easily. Doing so would make it possible to change what each item in the menu does when selected.
For those interested, below you can find an example code of how I got the widget behaviour I wanted. I took Bryan's advice to replace the OptionMenu for a Menubutton/Menu combination. I also made use of this post to find duplicate entries in my process order list.
Any thoughts or suggestions on how to implement this in a cleaner or shorter way, or how to get the same functionality with a different interface (e.g. drag and drop), are ofcourse welcome!
import tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W)
self.create_widgets()
def create_widgets(self):
# Assisting text
l1 = tk.Label(self, text = "Data in", font=(None, 15))
l1.grid(row=0, column=2)
l2 = tk.Label(self, text = u'\N{BLACK DOWN-POINTING TRIANGLE}', font=(None, 15))
l2.grid(row=1, column=2)
l3 = tk.Label(self, text = "Data out", font=(None, 15))
l3.grid(row=11, column=2)
l4 = tk.Label(self, text = u'\N{BLACK DOWN-POINTING TRIANGLE}', font=(None, 15))
l4.grid(row=10, column=2)
# Process list
self.active_procs = ['proc a','proc b','proc c','proc d',
'proc e','proc f','proc g','proc h']
self.the_value, self.widgets, self.topmenu = dict(), dict(), dict()
for index in range(8):
name_construct = 'nr' + str(index)
self.the_value[name_construct] = tk.StringVar(root)
self.the_value[name_construct].set(self.active_procs[index])
self.widgets[name_construct] = tk.Menubutton(self, textvariable=
self.the_value[name_construct],
indicatoron=True)
self.topmenu[name_construct] = tk.Menu(self.widgets[name_construct],
tearoff=False)
self.widgets[name_construct].configure(menu=self.topmenu[name_construct])
for proc in self.active_procs:
self.topmenu[name_construct].add_radiobutton(label=proc, variable=
self.the_value[name_construct],
command=lambda proc=proc,
widget=name_construct:
self.order_change(proc,widget))
self.widgets[name_construct].grid(row=index+2, column=2, columnspan=2,
sticky="nwse", padx=10, pady=10)
def order_change(self,proc,widget):
# Get the index of the last changed Menubutton
index_user_change = list(self.widgets.keys()).index(widget)
procs_order = [] # Current order from widgets
for index in range(8):
name_construct = 'nr' + str(index)
procs_order.append(self.widgets[name_construct].cget("text"))
# 1 change may lead to 1 double and 1 missing process
doubles = self.list_duplicates_of(procs_order,proc)
if len(doubles) == 2: # If double processes are present...
doubles.remove(index_user_change) # ...remove user input, change the other
missing_proc = str(set(self.active_procs)^set(procs_order)).strip('{"\'}')
index_change_along = int(doubles[0])
# Update references
self.active_procs[index_user_change] = proc
self.active_procs[index_change_along] = missing_proc
# Update widgets
name_c2 = 'nr'+str(index_change_along)
self.the_value[name_c2].set(self.active_procs[index_change_along])
self.widgets[name_c2].configure(text=missing_proc)
def list_duplicates_of(self,seq,item):
start_at = -1
locs = []
while True:
try:
loc = seq.index(item,start_at+1)
except ValueError:
break
else:
locs.append(loc)
start_at = loc
return locs
root = tk.Tk()
root.title("OptionMenu test")
app = Application(master=root)
root.mainloop()

Resources