1

I am currently working on a basic calculator program. I am trying to use the validate function so the user is only able to enter in values from the valild_input list. The test_input function which contains this list works fine until I decide to type in "=" or press the equals button. When I press the equals_button the current equation on the display entry isn't deleted and replaced with the result. Although this doesn't occur when I press the "=" key on the keyboard. The only issue is that the equals sign stays on the display and after that, the entry widget stops validating the user's input altogether.

from tkinter import *
from tkinter import messagebox

def replace_text(text):
    display.delete(0, END)
    display.insert(0, text)

#Calculates the input in the display        
def calculate(event = None):
    equation = display.get()
    try:
        result = eval(equation)
        replace_text(result)
    except: 
        messagebox.showerror("Error", "Math Error", parent = root)

def test_input(value, action):
    valid_input = ["7", "8", "9", "+", "4", "5", "6", "-", "1", "2", "3", "*", "0", ".", "/"]
    if action == "1":
        if value not in valid_input:
            return False
        return True

root = Tk() 
root.title("Calculator testing")

display = Entry(root, font=("Helvetica", 16), justify = "right", validate = "key")
display.configure(validatecommand = (display.register(test_input), "%S", "%d"))
display.insert(0, "")
display.grid(column = 0, row = 0, columnspan = 4, sticky = "NSWE", padx = 10, pady = 10)
display.bind("=", calculate)

#Equals button
button_equal = Button(root, font = ("Helvetica", 14), text = "=", command = 
calculate, bg = "#c0ded9")
button_equal.grid(column = 2, row = 1, columnspan = 2, sticky = "WE")

#All clear button 
button_clear = Button(root, font = ("Helvetica", 14), text = "AC", command = lambda: replace_text(""), bg = "#c0ded9")
button_clear.grid(column = 0, row = 1, columnspan = 2, sticky = "WE")

#Main Program       
root.mainloop()
xPythonNoob
  • 23
  • 1
  • 5
  • 2
    @ReblochonMasque I'm not sure whether this question deserves to be closed as a duplicate. Yes it's about validation, but the question is not about how to get validation working, because it is working when you start running the program. However, sometimes validation "breaks", which is not explained by the question you linked (ok it's there somewhere, but the OP will probably never find it without more guidance). – fhdrsdg Sep 03 '18 at 08:19
  • ok, I see your point @fhdrsdg – Reblochon Masque Sep 03 '18 at 08:30
  • 1
    From @ReblochonMasque's proposed duplicate: *"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."* The `test_input` doesn't do that - there's a branch where it returns nothing, i.e. `None`. – Aran-Fey Sep 03 '18 at 08:48

1 Answers1

4

There are 2 problems with your code.

  1. The validation function should always return a boolean.

    From this answer:

    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.

    Your test_input function doesn't do that - there's a branch in which it returns None.

    def test_input(value, action):
        valid_input = ["7", "8", "9", "+", "4", "5", "6", "-", "1", "2", "3", "*", "0", ".", "/"]
        if action == "1":
            if value not in valid_input:
                return False
            return True
        # None is being returned here!
    

    This is why the validation is disabled after your program deletes text from the Entry. The fix is simple: return True instead of None.

    def test_input(value, action):
        valid_input = ["7", "8", "9", "+", "4", "5", "6", "-", "1", "2", "3", "*", "0", ".", "/"]
        if action == "1":
            if value not in valid_input:
                return False
            return True
    
        # if action != 1, allow it
        return True
    
  2. The validation function needs to handle multi-character input.

    You've assumed that the validation function is called for every single character that's entered. This is true when the user types a formula with their keyboard, but not when copy/pasting or setting the entry's text with .insert(...). Your function needs to handle these cases.

    def test_input(value, action):
        valid_input = ["7", "8", "9", "+", "4", "5", "6", "-", "1", "2", "3", "*", "0", ".", "/"]
        if action == "1":
            return all(char in valid_input for char in value)
    
        # if action != 1, allow it
        return True
    
Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
  • I did try your solution, but the result is not being displayed when I press equals button the validations thinks the replaced text isn't a valid input. – xPythonNoob Sep 03 '18 at 22:53
  • The validation only works for one digit results so like results from 0-9 and stops working for a result of 10 and onwards. – xPythonNoob Sep 03 '18 at 23:11