Interactively validating Entry widget content in tkinter

Posted on

Solving problem is about exposing yourself to as many situations as possible like Interactively validating Entry widget content in tkinter and practice these strategies over and over. With time, it becomes second nature and a natural way you approach any problems in general. Big or small, always start with a plan, use other strategies mentioned here till you are confident and ready to code the solution.
In this post, my aim is to share an overview the topic about Interactively validating Entry widget content in tkinter, which can be followed any time. Take easy to follow this discuss.

Interactively validating Entry widget content in tkinter

What is the recommended technique for interactively validating content in a tkinter Entry widget?

I’ve read the posts about using validate=True and validatecommand=command, and it appears that these features are limited by the fact that they get cleared if the validatecommand command updates the Entry widget’s value.

Given this behavior, should we bind on the KeyPress, Cut, and Paste events and monitor/update our Entry widget’s value through these events? (And other related events that I might have missed?)

Or should we forget interactive validation altogether and only validate on FocusOut events?

Asked By: Malcolm


Answer #1:

The correct answer is, use the validatecommand attribute of the widget. Unfortunately this feature is severely under-documented in the Tkinter world, though it is quite sufficiently documented in the Tk world. Even though it’s not documented well, it has everything you need to do validation without resorting to bindings or tracing variables, or modifying the widget from within the validation procedure.

The trick is to know that you can have Tkinter pass in special values to your validate command. These values give you all the information you need to know to decide on whether the data is valid or not: the value prior to the edit, the value after the edit if the edit is valid, and several other bits of information. To use these, though, you need to do a little voodoo to get this information passed to your validate command.

Note: it’s important that the validation command returns either True or False. Anything else will cause the validation to be turned off for the widget.

Here’s an example that only allows lowercase (and prints all those funky values):

import tkinter as tk  # python 3.x
# import Tkinter as tk # python 2.x
class Example(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        # valid percent substitutions (from the Tk entry man page)
        # note: you only have to register the ones you need; this
        # example registers them all for illustrative purposes
        # %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.register(self.onValidate),
                '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
        self.entry = tk.Entry(self, validate="key", validatecommand=vcmd)
        self.text = tk.Text(self, height=10, width=40)
        self.entry.pack(side="top", fill="x")
        self.text.pack(side="bottom", fill="both", expand=True)
    def onValidate(self, d, i, P, s, S, v, V, W):
        self.text.delete("1.0", "end")
        self.text.insert("end","d='%s'n" % d)
        self.text.insert("end","i='%s'n" % i)
        self.text.insert("end","P='%s'n" % P)
        self.text.insert("end","s='%s'n" % s)
        self.text.insert("end","S='%s'n" % S)
        self.text.insert("end","v='%s'n" % v)
        self.text.insert("end","V='%s'n" % V)
        self.text.insert("end","W='%s'n" % W)
        # Disallow anything but lowercase letters
        if S == S.lower():
            return True
            return False
if __name__ == "__main__":
    root = tk.Tk()
    Example(root).pack(fill="both", expand=True)

For more information about what happens under the hood when you call the register method, see Input validation tkinter

Answered By: Bryan Oakley

Answer #2:

After studying and experimenting with Bryan’s code, I produced a minimal version of input validation. The following code will put up an Entry box and only accept numeric digits.

from tkinter import *
root = Tk()
def testVal(inStr,acttyp):
    if acttyp == '1': #insert
        if not inStr.isdigit():
            return False
    return True
entry = Entry(root, validate="key")
entry['validatecommand'] = (entry.register(testVal),'%P','%d')

Perhaps I should add that I am still learning Python and I will gladly accept any and all comments/suggestions.

Answered By: user1683793

Answer #3:

Use a Tkinter.StringVar to track the value of the Entry widget. You can validate the value of the StringVar by setting a trace on it.

Here’s a short working program that accepts only valid floats in the Entry widget.

from Tkinter import *
root = Tk()
sv = StringVar()
def validate_float(var):
    new_value = var.get()
        new_value == '' or float(new_value)
        validate.old_value = new_value
validate.old_value = ''
# trace wants a callback with nearly useless parameters, fixing with lambda.
sv.trace('w', lambda nm, idx, mode, var=sv: validate_float(var))
ent = Entry(root, textvariable=sv)
Answered By: Steven Rumbalski

Answer #4:

While studying Bryan Oakley’s answer, something told me that a far more general solution could be developed. The following example introduces a mode enumeration, a type dictionary, and a setup function for validation purposes. See line 48 for example usage and a demonstration of its simplicity.

#! /usr/bin/env python3
import enum
import inspect
import tkinter
from tkinter.constants import *
Mode = enum.Enum('Mode', 'none key focus focusin focusout all')
CAST = dict(d=int, i=int, P=str, s=str, S=str,
            v=Mode.__getitem__, V=Mode.__getitem__, W=str)
def on_validate(widget, mode, validator):
    if mode not in Mode:
        raise ValueError('mode not recognized')
    parameters = inspect.signature(validator).parameters
    if not set(parameters).issubset(CAST):
        raise ValueError('validator arguments not recognized')
    casts = tuple(map(CAST.__getitem__, parameters))
    widget.configure(, validatecommand=[widget.register(
        lambda *args: bool(validator(*(cast(arg) for cast, arg in zip(
            casts, args)))))]+['%' + parameter for parameter in parameters])
class Example(tkinter.Frame):
    def main(cls):
        root = tkinter.Tk()
        root.title('Validation Example')
        root.grid_rowconfigure(0, weight=1)
        root.grid_columnconfigure(0, weight=1)
    def __init__(self, master, **kw):
        super().__init__(master, **kw)
        self.entry = tkinter.Entry(self)
        self.text = tkinter.Text(self, height=15, width=50,
                                 wrap=WORD, state=DISABLED)
        self.entry.grid(row=0, column=0, sticky=NSEW)
        self.text.grid(row=1, column=0, sticky=NSEW)
        self.grid_rowconfigure(1, weight=1)
        self.grid_columnconfigure(0, weight=1)
        on_validate(self.entry, Mode.key, self.validator)
    def validator(self, d, i, P, s, S, v, V, W):
        self.text['state'] = NORMAL
        self.text.delete(1.0, END)
        self.text.insert(END, 'd = {!r}ni = {!r}nP = {!r}ns = {!r}n'
                              'S = {!r}nv = {!r}nV = {!r}nW = {!r}'
                         .format(d, i, P, s, S, v, V, W))
        self.text['state'] = DISABLED
        return not S.isupper()
if __name__ == '__main__':
Answered By: Noctis Skytower

Answer #5:

Bryan’s answer is correct, however no one mentioned the ‘invalidcommand’ attribute of the tkinter widget.

A good explanation is here:

Text copy/pasted in case of broken link

The Entry widget also supports an invalidcommand option that specifies a callback function that is called whenever the validatecommand returns False. This command may modify the text in the widget by using the .set() method on the widget’s associated textvariable. Setting up this option works the same as setting up the validatecommand. You must use the .register() method to wrap your Python function; this method returns the name of the wrapped function as a string. Then you will pass as the value of the invalidcommand option either that string, or as the first element of a tuple containing substitution codes.

There is only one thing that I cannot figure out how to do: If you add validation to an entry, and the user selects a portion of the text and types a new value, there is no way to capture the original value and reset the entry. Here’s an example

  1. Entry is designed to only accept integers by implementing ‘validatecommand’
  2. User enters 1234567
  3. User selects ‘345’ and presses ‘j’. This is registered as two actions: deletion of ‘345’, and insertion of ‘j’. Tkinter ignores the deletion and acts only on the insertion of ‘j’. ‘validatecommand’ returns False, and the values passed to the ‘invalidcommand’ function are as follows: %d=1, %i=2, %P=12j67, %s=1267, %S=j
  4. If the code does not implement an ‘invalidcommand’ function, the ‘validatecommand’ function will reject the ‘j’ and the result will be 1267. If the code does implement an ‘invalidcommand’ function, there is no way to recover the original 1234567.
Answered By: orionrobert

Answer #6:

Here is a simple way to validate the entry value, which allows user to enter digits only:

import tkinter  # imports Tkinter module
root = tkinter.Tk()  # creates a root window to place an entry with validation there
def only_numeric_input(P):
    # checks if entry's value is an integer or empty and returns an appropriate boolean
    if P.isdigit() or P == "":  # if a digit was entered or nothing was entered
        return True
    return False
my_entry = tkinter.Entry(root)  # creates an entry
my_entry.grid(row=0, column=0)  # shows it in the root window using grid geometry manager
callback = root.register(only_numeric_input)  # registers a Tcl to Python callback
my_entry.configure(validate="key", validatecommand=(callback, "%P"))  # enables validation
root.mainloop()  # enters to Tkinter main event loop

PS: This example can be very useful for creating an app like calc.

Answered By: Demian Wolf

Answer #7:

import tkinter
def only_numeric_input(e):
    #this is allowing all numeric input
    if e.isdigit():
        return True
    #this will allow backspace to work
    elif e=="":
        return True
        return False
#this will make the entry widget on root window
#arranging entry widget on screen
#very usefull for making app like calci
Answered By: Mohammad Omar

Answer #8:

Responding to orionrobert’s problem of dealing with simple validation upon substitutions of text through selection, instead of separate deletions or insertions:

A substitution of selected text is processed as a deletion followed by an insertion. This may lead to problems, for example, when the deletion should move the cursor to the left, while a substitution should move the cursor to the right. Fortunately, these two processes are executed immediately after one another.
Hence, we can differentiate between a deletion by itself and a deletion directly followed by an insertion due to a substitution because the latter has does not change the idle flag between deletion and insertion.

This is exploited using a substitutionFlag and a Widget.after_idle().
after_idle() executes the lambda-function at the end of the event queue:

class ValidatedEntry(Entry):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.tclValidate = (self.register(self.validate), '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
        # attach the registered validation function to this spinbox
        self.config(validate = "all", validatecommand = self.tclValidate)
    def validate(self, type, index, result, prior, indelText, currentValidationMode, reason, widgetName):
        if typeOfAction == "0":
            # set a flag that can be checked by the insertion validation for being part of the substitution
            self.substitutionFlag = True
            # store desired data
            self.priorBeforeDeletion = prior
            self.indexBeforeDeletion = index
            # reset the flag after idle
            self.after_idle(lambda: setattr(self, "substitutionFlag", False))
            # normal deletion validation
        elif typeOfAction == "1":
            # if this is a substitution, everything is shifted left by a deletion, so undo this by using the previous prior
            if self.substitutionFlag:
                # restore desired data to what it was during validation of the deletion
                prior = self.priorBeforeDeletion
                index = self.indexBeforeDeletion
                # optional (often not required) additional behavior upon substitution
                # normal insertion validation
        return True

Of course, after a substitution, while validating the deletion part, one still won’t know whether an insert will follow.
Luckily however, with:
we can achieve most desired behavior retrospectively (since the combination of our new substitutionFlag with an insertion is a new unique and final event.

Answered By: Stendert

Leave a Reply

Your email address will not be published. Required fields are marked *