0

I am currently working on a calculator program. I have managed to make the validation code function so the entry widget only receives values from valid_input list. Although, I am currently trying to prevent inputs such as "5**2" and "2//2", is there a way to use the validation code or test_input function to make the user unable to enter in two of the same operators. This is mainly regarding the division and multiplication operator.

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)
        print(result) #Just for reference 
        return True 
    except: 
        messagebox.showerror("Error", "Math Error", parent = root)

#This function dosen't allow the user to input invalid values    
def test_input(value, action):
    #list of inputs that is valid for the calculator to function
    valid_input = ["7", "8", "9", "+", "4", "5", "6", "-", "1", "2", "3", "*", "0", ".", "/"]
    if action == "1": #If an insertion is occuring in the entry
        return all(char in valid_input for char in value)
    # if action != 1, allow it
    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 Answers2

3

In the validation function you can split up the logic for inserting a number and inserting an operator/point.

Since you care about what's already in the entry and the place you are inserting a character, you should pass some more info to the validatecommand. The information you would need is (from this answer):

# %i = index of char string to be inserted/deleted, or -1
# %s = value of entry prior to editing
# %S = the text string being inserted or deleted, if any

You can then do several checks to disallow anything that would insert two operators or point after another:

def test_input(insert, content, index, action):
    #list of inputs that is valid for the calculator to function
    valid_numbers = ["7", "8", "9", "4", "5", "6", "1", "2", "3", "0"]
    valid_chars = ["+", "-", "*", ".", "/"]
    index = int(index)
    if action != "1": # Always allow if it's not an insert
        return True
    if insert in valid_numbers: # Always allow a number
        return True
    if insert in valid_chars: # If it's an operator or point do further checks
        if index==0: # Disallow if it's the first character
            return False
        if content[index-1] in valid_chars: # Disallow if the character before is an operator or point
            return False
        if index != len(content): # If you're not at the end
            if content[index] in valid_chars: # Disallow if the next character is an operator or point
                return False
        return True # Allow if it's none of the above
    else:
        return False # Disallow if the character is not a number, operator or point

with

display.configure(validatecommand = (display.register(test_input), "%S", "%s", "%i", "%d"))

I forgot that this messes up insertion of the answer, since I assume only one character to be inserted at a time. You can fix this in (at least) two ways:

You can turn off validation for inserting the answer and turn it back on when you have inserted:

display.configure(validate='none')
display.insert(0, text)
display.configure(validate='key')

Or, since answers are always all numbers, you can change the second if in the validation command to allow multiple numbers instead of only one:

if all(char in valid_numbers for char in insert):
fhdrsdg
  • 10,297
  • 2
  • 41
  • 62
1

You can use regex to validate these kind of strings:

import re

_str = "3*42"

print(re.match(r'[0-9]+\.?[0-9]*[\+\-\*\/][0-9]+\.?[0-9]*', _str))

output:

<_sre.SRE_Match object; span=(0, 4), match='3*42'>

as you can see, re.match returns an object if the RegEx is matching, else it returns None.

With this information, we can check if the match returns something to decide if a string is valid or not:

import re


examples = ["3*42",       # VALID
            "3.14-42.0",  # VALID
            "12.0.0+21",  # INVALID - 2 dots
            "3--42",      # INVALID - 2 operators
            "3,14/42",    # INVALID - comma instead of dot
            "123 456",    # INVALID - no operator
            ]


for operation in examples:
    print("Operation {0} is: ".format(operation), end='')
    if re.match(r'[0-9]+\.?[0-9]*[\+\-\*\/][0-9]+\.?[0-9]*', operation):
        print("VALID")
    else:
        print("INVALID")

output:

Operation 3*42 is: VALID
Operation 3.14-42.0 is: VALID
Operation 12.0.0+21 is: INVALID
Operation 3--42 is: INVALID
Operation 3,14/42 is: INVALID
Operation 123 456 is: INVALID

Here's how the RegEx works:
[0-9]+: Matches one ore more digits
\.?: Matches zero or one dot
[0-9]*: Matches zero or more digits
[\+\-\*\/]: Marches exactly one of this four symbols (+-*/)
[0-9]+: Matches one ore more digits
\.?: Matches zero or one dot
[0-9]*: Matches zero or more digits

If we put everything togheter, we're asking for:

One or more digits, followed by zero or one dots, followed by zero or more digits, followed by an operator, followed by One or more digits, followed by zero or one dots, followed by zero or more digits

and just as a hint, you can change your valid_input with a RegEx:

r`[0-9\.\+\-\*\/]*`
Gsk
  • 2,929
  • 5
  • 22
  • 29