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.