How do I get an event callback when a Tkinter Entry widget is modified?
Exactly as the question says. Text
widgets have the <<Modified>>
event, but Entry
widgets don't appear to.
Solution 1:
Add a Tkinter StringVar to your Entry widget. Bind your callback to the StringVar using the trace method.
from Tkinter import *
def callback(sv):
print sv.get()
root = Tk()
sv = StringVar()
sv.trace("w", lambda name, index, mode, sv=sv: callback(sv))
e = Entry(root, textvariable=sv)
e.pack()
root.mainloop()
Solution 2:
At the time of writing (2017, Python 3.6, tkinter version 8.6.6) the docs suggest that .trace
is deprecated. The suggested form now seems to be:
sv.trace_add("write", callback)
This works very well if you want notification whenever the variable is changed. However, my application just wants notification when he user finishes editing the text. I found piggy-backing on the "validation" mechanism to work well here:
from tkinter import *
root = Tk()
sv = StringVar()
def callback():
print(sv.get())
return True
e = Entry(root, textvariable=sv, validate="focusout", validatecommand=callback)
e.grid()
e = Entry(root)
e.grid()
root.mainloop()
This will invoke callback
whenever the entry widget loses focus (I added a 2nd entry widget so the 1st one can actually lose focus!)
Solution 3:
Thanks Steven! Russell Owen's Tkinter Folklore explains how to get the StringVar value directly from the name argument (PY_VAR#) using globalgetvar(), but not how to map the name to a widget. Your lambda method of changing the callback args is like magic (to us Python newbies, at least).
When there is more than one Entry, it is often necessary to know not just the value, but which Entry was changed. Expanding on Steven's example slightly, the following (Python3) passes an index which can be used to keep track of multiple Entries.
from tkinter import Tk, Frame, Label, Entry, StringVar
class Fruitlist:
def entryupdate(self, sv, i):
print(sv, i, self.fruit[i], sv.get())
def __init__(self, root):
cf = Frame(root)
cf.pack()
self.string_vars = []
self.fruit = ("Apple", "Banana", "Cherry", "Date")
for f in self.fruit:
i = len(self.string_vars)
self.string_vars.append(StringVar())
self.string_vars[i].trace("w", lambda name, index, mode, var=self.string_vars[i], i=i:
self.entryupdate(var, i))
Label(cf, text=f).grid(column=2, row=i)
Entry(cf, width=6, textvariable=self.string_vars[i]).grid(column=4, row=i)
root = Tk()
root.title("EntryUpdate")
app = Fruitlist(root)
root.mainloop()
Solution 4:
You can also use the KeyRelease event that will be fired every time when the user clicks on the widget.
Than you can than filter for changes.
from tkinter import *
from tkinter import ttk
class GUI():
def __init__(self):
self.root = Tk()
self.sv = StringVar()
self.prevlaue=''
#entry
self.entry = ttk.Entry(self.root, width=30, textvariable =self.sv)
self.entry.grid(pady=20,padx=20)
self.entry.bind("<KeyRelease>", self.OnEntryClick) #keyup
self.root.mainloop()
def OnEntryClick(self, event):
value=self.sv.get().strip()
changed = True if self.prevlaue != value else False
print(value, 'Text has changed ? {}'.format(changed))
self.prevlaue = value
#create the gui
GUI()
I hope it helps.