18

I need to restrict the values in the Entry widget to numbers only. The way I implemented is:

import numpy as np
from Tkinter import *;
import tkMessageBox;

class window2:

    def __init__(self,master1):

        self.panel2=Frame(master1)
        self.panel2.grid()

        self.button2=Button(self.panel2,text="Quit",command=self.panel2.quit)
        self.button2.grid()

        self.text1=Entry(self.panel2)
        self.text1.grid()
        self.text1.bind('<KeyPress>', self.keybind1)
        self.text1.focus()

    def keybind1 (self,event):
        if event.int in np.linspace(0,9,10):
            print event.int


root1=Tk()
window2(root1)
root1.mainloop()

I keep getting error message that Event instance has no attribute 'int'. What should I do?

sigma.z.1980
  • 469
  • 2
  • 7
  • 12

10 Answers10

30

I realize this is quite late for an answer but feel that I can give a lot simpler answer to this... it is really quite simple once you understand how it works.

Use the validating feature that comes with the Entry widget.

Lets assume self is a widget:

vcmd = (self.register(self.callback))

w = Entry(self, validate='all', validatecommand=(vcmd, '%P')) 
w.pack()

def callback(self, P):
    if str.isdigit(P) or P == "":
        return True
    else:
        return False

You don't need to include all of the substitution codes: ('%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W'), only the ones you'll use are necessary.

The Entry widget returns a string so you will have to somehow extract any digits in order to separate them from other characters. The easiest way to do this is to use str.isdigit(). This is a handy little tool built right into python's libraries and needs no extra importing and it will identify any numerics (digits) that it finds from the string that the Entry widget returns.

The or P == "" part of the if statement allows you to delete your entire entry, without it, you would not be able to delete the last (1st in entry box) digit due to '%P' returning an empty value and causing your callback to return False. I won't go into detail why here.

validate='all' allows the callback to evaluate the value of P as you focusin, focusout or on any key stroke changing the contents in the widget and therefore you don't leave any holes for stray characters to be mistakenly entered.

In all, to make things simple. If your callback returns True it will allow data to be entered. If the callback returns 'False` it will essentially 'ignore' keyboard input.

Check out these two references. They explain what each substitution code means and how to implement them.

http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html http://stupidpythonideas.blogspot.ca/2013/12/tkinter-validation.html

EDIT: This will only take care of what is allowed in the box. You can, however, inside the callback, add whatever value P has to any variable you wish.

lyxαl
  • 1,108
  • 1
  • 16
  • 26
Ernie Peters
  • 537
  • 1
  • 6
  • 18
29

This uses validatecommand to restrict valid user input in the tk.Entry to strings which can be interpreted as floats:

import tkinter as tk

class window2:
    def __init__(self, master1):
        self.panel2 = tk.Frame(master1)
        self.panel2.grid()
        self.button2 = tk.Button(self.panel2, text = "Quit", command = self.panel2.quit)
        self.button2.grid()
        vcmd = (master1.register(self.validate),
                '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
        self.text1 = tk.Entry(self.panel2, validate = 'key', validatecommand = vcmd)
        self.text1.grid()
        self.text1.focus()

    def validate(self, action, index, value_if_allowed,
                       prior_value, text, validation_type, trigger_type, widget_name):
        if value_if_allowed:
            try:
                float(value_if_allowed)
                return True
            except ValueError:
                return False
        else:
            return False

root1 = tk.Tk()
window2(root1)
root1.mainloop()

References:

  • The Tk man page explains the validate and validatecommand options. (Thanks to schlenk for the link).
  • I learned how to do this in Python here.
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • The Tk documentation about validation might help: http://www.tcl.tk/man/tcl8.5/TkCmd/entry.htm#M-validate – schlenk Jan 22 '12 at 12:16
  • Thanks for the link, schlenk. – unutbu Jan 22 '12 at 12:32
  • 1
    How to handle deleting all characters in the entry field? I tried `if action == 0: return True`, but it didn't work. However, if I check `if value_if_allowed == '': return True` after `except ValueError:`, it does allow me to delete the contents of the field. Weird. – alkamid Nov 11 '16 at 10:35
  • why backspace to remove all data entered isn't working here. one value is always there even after pressing backspace once a value is typed. ! – master_yoda Jul 16 '19 at 10:14
  • 1
    @sunny_old_days: Thanks for the bug report. `float(value_if_allowed)` was raising `ValueError` when `value_if_allowed` equaled an empty string. I've changed the code to avoid this `ValueError`, thus allowing empty strings. – unutbu Jul 16 '19 at 11:28
11

The answer is almost perfect, just a little addition to allow for deleting the whole string. The check for floats should be done only when inserting text

def validate_float(self, action, index, value_if_allowed,
    prior_value, text, validation_type, trigger_type, widget_name):
    # action=1 -> insert
    if(action=='1'):
        if text in '0123456789.-+':
            try:
                float(value_if_allowed)
                return True
            except ValueError:
                return False
        else:
            return False
    else:
        return True
capitalaslash
  • 240
  • 4
  • 10
3

I'm new to python and Tkinker, but this works best for me:

    def keybind1 (self,event):
        v = event.char
        try:
            v = int(v)
        except ValueError:
            if v!="\x08" and v!="":
                return "break"

The v = int(v) triggers a ValueError on any key other then number keys, but the if v!="\x08 and v!="":" statement still allows the backspace, ("\x08"), and delete, arrow, home, end, etc. keys, (which have an event.char of ""), to work normally - otherwise the break command stops the entry of other characters into the Entry widget.

gint
  • 31
  • 2
1

I had to deal with an initial insert case as well. This is what I have ended up with:

    def _checkNumberOnly(self, action, value_if_allowed):
        if action != '1':
           return True
        try:
            return value_if_allowed.isnumeric()
        except ValueError:
           return False

    vcmd = (self.register(self._checkNumberOnly), '%d', '%P')
        self.port = ttk.Entry(self, width=35, validate='key', validatecommand=vcmd)

Therefore it validates for the following:

    self.port.insert(0, '6379')

I'm not sure that the catch is needed, due to isnumeric() not stating it raises an exception.

Smurf-IV
  • 91
  • 1
  • 3
0

if you are dealing with locales that have a comma as decimal point:

locale.setlocale(locale.LC_ALL,'de_DE.UTF-8') # German

vcmd = (self.root.register(self.entry_numericonly), '%d', '%P')

self.my_float_entry = tk.Entry(self.root, ... , validate='key', validatecommand=vcmd)

def entry_numericonly(self, action, value_if_allowed):
    if(action == "1"):
        try:
            loc_float  = locale.atof(value_if_allowed)
            loc_float_format = locale.format("%f", loc_float)
            try:
                loc_same_length = loc_float_format[:len(value_if_allowed)]
                return value_if_allowed == loc_same_length
            except:
                return False                    
        except:
            return False
    else:
        return True
A.J.Bauer
  • 2,803
  • 1
  • 26
  • 35
0
def validate(entry_text):
    chars = '1234567890'
    if any((c not in chars) for c in pin.get()):
        lpin = int(len(pin.get()))-1 
        pin.set(pin.get()[:lpin])

pin.trace("w", lambda *args: validate(pin))
0

""" Below code restricts the ttk.Entry widget to receive type 'str'. """

    import tkinter as tk
    from tkinter import ttk


    def is_type_int(*args):
      item = var.get()
      try:
        item_type = type(int(item))
        if item_type == type(int(1)):
          print(item)
          print(item_type)
      except:
        ent.delete(0, tk.END)


    root = tk.Tk()
    root.geometry("300x300")

    var = tk.StringVar()

    ent = ttk.Entry(root, textvariable=var)
    ent.pack(pady=20)

    var.trace("w", is_type_int)

    root.mainloop()
osaid
  • 1
  • 1
0

Here is a complete and simplified working code answering the OP, where the Entry widget accepts only integers while allowing complete backspacing for re-entry. It is a variation on answers by Ernie Peters and unutbu, above, and those from a similar issue addressed at Interactively validating Entry widget content in tkinter.

In enter_only_digits(), parameter values provided from the vcmd statement are %P, the value of the entry when entry editing is allowed, and %d, the desired "insert" action type 1.

import tkinter as tk

class window2:
    def __init__(self, master1):
        self.panel2 = tk.Frame(master1)

        self.button2 = tk.Button(self.panel2, text="Quit", command=self.panel2.quit)

        vcmd = (master1.register(self.enter_only_digits), '%P', '%d')
        self.text1 = tk.Entry(self.panel2, validate='key', validatecommand=vcmd)

        self.panel2.grid()
        self.button2.grid()
        self.text1.grid()
        self.text1.focus()


    def enter_only_digits(self, entry, action_type) -> bool:
        if action_type == '1' and not entry.isdigit():
            return False

        return True
    

root1 = tk.Tk()
window2(root1)
root1.mainloop()
Doc
  • 98
  • 4
0

This adds to capitalaslash's answer but allows the first character to be '-'.

import tkinter as tk

class window2:
    def __init__(self, master1):
        self.panel2 = tk.Frame(master1)
        self.panel2.grid()
        self.button2 = tk.Button(self.panel2, text = "Quit", command = self.panel2.quit)
        self.button2.grid()
        vcmd = (master1.register(self.validate),
                '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
        self.text1 = tk.Entry(self.panel2, validate = 'key', validatecommand = vcmd)
        self.text1.grid()
        self.text1.focus()

    def validate(self, action, index, value_if_allowed,
                       prior_value, text, validation_type, trigger_type, widget_name):
        # action=1 -> insert
        print(index)
        if action == '1':
            if text in '0123456789-.':

                try:
                    if text == '-' and index == '0':
                        print('returning')
                        return True
                    float(value_if_allowed)
                    return True
                except ValueError:
                    return False
            else:
                return False
        else:
            return True

root1 = tk.Tk()
window2(root1)
root1.mainloop()