How do I get an event callback when a Tkinter Entry widget is modified?

Question:

Exactly as the question says. Text widgets have the <<Modified>> event, but Entry widgets don’t appear to.

Asked By: bfops

||

Answers:

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()  
Answered By: Steven Rumbalski

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() 
Answered By: Dave

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!)

Answered By: Matthew Daws

I know other variant.

before i input the code, it could better explain path of coding: read here

and there is my code:

from Tkinter import *

class ttt:
   def __init__(self):
      self.str1 = StringVar()
      self.e1 = Entry(root, textvariable=self.str1)
      self.str1.trace('w', self.callback_1)
      self.e1.pack()

      self.str2 = StringVar()
      self.e2 = Entry(root, textvariable=self.str2, state='readonly')
      self.e2.pack()

      self.str3 = StringVar()
      self.e3 = Entry(root, textvariable=self.str3, state='readonly')
      self.e3.pack()

      bt = Button(root, text = 'ещё', command = self.callback_2)
      bt.pack()

   def callback_1(self, name='', index='', mode=''):
      tmp = self.str1.get()
      if tmp:
         self.str2.set(int(tmp) * 6)
         print self.str2.get()

   def callback_2(self, name='', index='', mode=''):
      tmp = self.str1.get()
      if tmp:
         self.str3.set(int(tmp) * 6)
         print self.str3.get()   

root = Tk()
t = ttt()
root.mainloop()

there is 2 variant: by press button and enter to entry.
now you can select any variant

Answered By: Konstantin Kozlenko

I use Python 3.6 and could not get .trace to work. The following code allows a default value of StringVar to be accepted or edited. on_changed is called when the return key is pressed.

from tkinter import Tk, LEFT, BOTH, StringVar
from tkinter.ttk import Entry, Frame


class Example(Frame):
    def __init__(self, parent):
        Frame.__init__(self, parent)
        self.parent = parent
        self.initUI()

    def initUI(self):
        self.parent.title("Entry")
        self.pack(fill=BOTH, expand=1)
        self.contents = StringVar()
        # give the StringVar a default value
        self.contents.set('test')
        self.entry = Entry(self)
        self.entry.pack(side=LEFT, padx=15)
        self.entry["textvariable"] = self.contents
        self.entry.bind('<Key-Return>', self.on_changed)

    def on_changed(self, event):
        print('contents: {}'.format(self.contents.get()))
        return True


def main():
    root = Tk()
    ex = Example(root)
    root.geometry("250x100+300+300")
    root.mainloop()


if __name__ == '__main__':
    main()
Answered By: Oppy

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.

Answered By: Avi ba

I found using the built in TK Validation fits my beginner level of python skill better. Validation of an Entry element is not well documented in tkinter itself, you must use a register() method to set a callback , and by always returning True from the registered validation callback, you are able to get a notification.

def filter_callback(self,new_value):
    print(new_value)
    # must return true since we want the validation events to keep coming
    return(True)

def __init__(self,root)
    self.edit_filter = ttk.Entry(root)
    # %d = Type of action (1=insert, 0=delete, -1 for others)
    # %i = index of char string to be inserted/deleted, or -1
    # %P = value of the entry if the edit is allowed
    # %s = value of entry prior to editing
    # %S = the text string being inserted or deleted, if any
    # %v = the type of validation that is currently set
    # %V = the type of validation that triggered the callback
    #      (key, focusin, focusout, forced)
    # %W = the tk name of the widget
    vcmd = (self.edit_filter.register(self.filter_callback), "%P")
    # notify key presses only
    self.edit_filter.config(validate = "key", validatecommand = vcmd) 

(illustrative code only)

Example answer here Interactively validating Entry widget content in tkinter , and some docs on the events here https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/entry-validation.html . It’s not less code than using trace, but may be easier for some beginners to follow and to build on.

Answered By: user337598

I have been able to do what I needed using the <FocusOut> event. Here is a sample:

import tkinter
from tkinter import ttk

def doSomething(event):
    text = event.widget.get()
    print(text)

window = tkinter.Tk()
testEntry = ttk.Entry(window)
testEntry.bind('<FocusOut>', doSomething)
testEntry.grid(column=0,row=0)
thingEntry = ttk.Entry(window)
thingEntry.bind('<FocusOut>', doSomething)
thingEntry.grid(column=0,row=1)
startButton = ttk.Button(window,text='OK',command=lambda: print('click'))
startButton.grid(column=0,row=2, sticky='W')

window.mainloop()
print('Bye Bye')

The important part is:

def doSomething(event):
    text = event.widget.get()
    print(text)

testEntry.bind('<FocusOut>', doSomething)

Using Entry.bind(event,function) you can attach an event listener, in this case the doSomething function. This function gets one parameter, an Event object. In turn, you can read what’s in the Entry widget using event.widget.get().

Every time you leave an Entry, its value is printed. This includes when you click on the button. As you will see from the test, the button’s event is triggered after the Entry’s event,

Answered By: Manngo

This is my take on this based on @Avi ba answer. Made it simpler by adding a setter and getter for text variable. Also avoided using StrinVar.

class CustomEntry(ttk.Entry):
    def __init__(self, parent, valueChangeCallback=lambda x: print(x), **kwargs):
        super().__init__(parent, **kwargs)

        # bind function callback when value of the text is changed
        self.bind("<KeyRelease>", lambda e: valueChangeCallback(self.text))


    @property
    def text(self) -> str:
        return self.get()


    @text.setter
    def text(self, value) -> None:
        self.delete(0, 'end')
        self.insert(0, value)
Answered By: Youstanzr
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.