keysanderaser
keysanderaser

Reputation: 13

tkinter Entry validatecommand and trace_add for a float that allows an empty input and triggers an update function

Input or paste only a float into a tk.Entry, except allowing an empty field, then trigger an action that passes the updated value. This should be generalizable.

import tkinter as tk

def float_validation(inStr,acttyp): #check for float     
    if acttyp == '1':
        try:
            float(inStr)
            return True 
        except ValueError:
            return False
    #Do NOT evaluate actype == '0' (delete action)
    #Using acttype == '0', try:float("") returns False
    return True

def update_from_entry(entry): #address empty string after float validation
    if not entry.get() == "":
        vals[strs.index(entry)].set(float(entry.get()))  
    else:
        vals[strs.index(entry)].set(0)
        
def calculate(entry): #do something after entry update
    update_from_entry(entry)
    calculated_value.set(entry1_val.get()+entry2_val.get())

root = tk.Tk()  
strs = [] #create lists of objects to index against each other
vals = []

entry1_str = tk.StringVar(value='0.123') 
strs.append(entry1_str) 
entry1_str.trace_add('write',lambda a,b,c, entry=entry1_str: calculate(entry)) 
entry1_input = tk.Entry(root, textvariable=entry1_str, validate='all')
entry1_input.grid(row=0,sticky='ew')
entry1_input['validatecommand'] = entry1_input.register(float_validation),'%P','%d'
entry1_val = tk.DoubleVar(value = float(entry1_str.get()))
vals.append(entry1_val)

entry2_str = tk.StringVar(value='0.456')
strs.append(entry2_str)
entry2_str.trace_add('write',lambda a,b,c,entry=entry2_str:calculate(entry))
entry2_input = tk.Entry(root, textvariable=entry2_str, validate='all')
entry2_input.grid(row=1,sticky='ew')
entry2_input['validatecommand'] = entry1_input.register(float_validation),'%P','%d'
entry2_val = tk.DoubleVar(value = float(entry2_str.get()))
vals.append(entry2_val)

calculated_value = tk.DoubleVar(value=entry1_val.get()+entry2_val.get())
output = tk.Label(root, textvariable=calculated_value).grid(row=2,sticky='ew')

root.mainloop() 

The code works, but is it over-contrived? Is this a reasonable way of limiting the tk.Entry to a float by introducing additional steps to address an empty tk.Entry validation of False from float(""), while triggering updates? Have I misunderstood how to allow empty (and/or pasting into) tk.Entry objects?

Upvotes: 0

Views: 53

Answers (1)

Tls Chris
Tls Chris

Reputation: 3824

Validating floats in Entry has a few edge cases apart from "". If negative numbers are concerned '-' needs to be accepted. If '.123' is valid '.' must be accepted. If exponents are accepted '{valid-decimal}E' and '{valid-decimal}e-' must be accepted.

import tkinter as tk

def to_float( str_in ):
    """  Converts str_in to a float if possible.
         Possible intermediate steps entering a float:
             It accepts "", '.', '-' and '-.'  returning zero
             It accepts 'digits{Ee}' and 'digits{eE}-' returning digits
             If str_in doesn't convert to a float it raises a ValueError
    """
    str_in = str_in or '0'  # Changes str_in to '0' if it starts as ""

    # Remove this block if these edge cases aren't a concern.
    if str_in[-1] in [ '-', '.', 'e', 'E' ] :  # if last character is in the list
        str_in += '0'
        # '-0', '.0', '-.0', '0E0' and '0E-0' are valid floats ( 0.0 )

    return float( str_in )          

def float_valid( str_in ):
    try:
        to_float( str_in )
        return True
    except ValueError:
        return False

root = tk.Tk()

validate_cmd = root.register( float_valid )

v1 = tk.StringVar()
v2 = tk.StringVar()
result = tk.StringVar( value = '0.0' )

e1 = tk.Entry( root, validate = 'all', validatecommand = (validate_cmd, '%P'),
               textvariable = v1 )
e2 = tk.Entry( root, validate = 'all', validatecommand = (validate_cmd, '%P'),
               textvariable = v2 )

res = tk.Label( root, textvariable = result )

def calc( *args ):
    result.set( to_float( v1.get() ) + to_float( v2.get() ) )

v1.trace( 'w', calc )
v2.trace( 'w', calc )

e1.grid( row = 0, column = 0, padx = 5, pady = 5 )
e2.grid( row = 0, column = 1, padx = 5, pady = 5 )
res.grid( row = 1, column = 0, columnspan = 2, padx = 5, pady = 5 )

root.mainloop()

Upvotes: 0

Related Questions