I'd really like to condense and simplify my code for validation which can be found below, but I can't think of any other way to do it. This code validates text being entered into the tkinter entry field.
def validate_input(self, entered_value, modify_type, index):#used to validate input entered into entry field.
current_input = str(self.text_box.get())
index_pos = int(index)
#Cheecks if the attempted modification type is insertion or deletion.
if modify_type == '1': #insert
if entered_value== ".":
if current_input == "":
return True#allows a decimal point to be inserted if the field is blank.
elif index_pos == len(current_input):
if current_input[-1] == ".":
return False#doesn't allow a decimal to be inserted if the last character is a decimal.
else:
return True
elif current_input[index_pos-1] == "." or current_input[index_pos] =="." :
return False#doesn't allow a decimal to be inserted if there is a decimal point on either side of the insert position.
else:
return True
if entered_value in "*/+-":
if current_input == "" and entered_value in "*/":
return False#doesn't allow a multiplication or division operator to be entered into an empty field.
elif current_input == "" and entered_value in "+-":
return True#allows an addition or subtraction operator to be entered for negative and positive numbers
if index_pos == len(current_input):#if character is being inserted at the end of the string
if current_input[-1] in "+-" and entered_value in "+-":
return True#allows two addition or subtraction signs in a row.
elif current_input[-1] == "." and entered_value in "+-":
return False#doesn't allow the insertion of + or - after a decimal point.
elif current_input[-1] in "*/+-." and entered_value in "*/":
return False#doesn't allow * or / to be entered after */+-
else:
return True
if entered_value in "+-":
if current_input[index_pos-1] in "*/+-" or current_input[index_pos] in "*/+-" :
return True#allows a + or a - to be inserted after or before another operator.
elif current_input[index_pos-1] == ".":
return False#doesn't allow a + or a - to be entered after a decimal point.
elif entered_value in "*/":
if current_input[index_pos-1] in "*/+-." or current_input[index_pos] in "*/+-" :
return False#doesn't allow a * or / to be entered if there is an operator or decimal before, or an operator after.
else:
return True
#Checks if entered value is in list of accepted values, stored in setup of CalculatorGUI class.
if entered_value in self.accepted_values:
return True
#Accepts all attempts to remove text from the entryfield.
elif modify_type == "0":#delete
return True
return False
Full code can be found below, thanks for your help! :)
from tkinter import *
from tkinter import messagebox
import re
class CalculatorFunctions:
def __init__(self, root):
self.root = root
def num_press(self, num):#function that runs if a number button is pressed
new_input = num
cursor_position = self.text_box.index(INSERT)#gets position of where number is trying to be inserted
self.text_box.insert(cursor_position, new_input)#inserts number at the cursor's position in the entry field
#Creates a message-box popup to display relevant author information.
def show_info_popup(self):
messagebox.showinfo("Author", "NAME", \nLast Edited: September 2018\nPython Version: 3.7.0")
#Command that clears everything in the calculator's entrybox.
def clear_screen(self):
self.text_box.delete(0, END)
#Removes the last character in the entry field.
def backspace(self):
current = str(self.text_box.get())
cursor_position = self.text_box.index(INSERT)
if cursor_position == 0:#if the insert position is at the beginning of the entry field (far left), don't backspace anything.
pass
else:
#deletes the text one index position before the insert position.
cursor_position -= 1
self.text_box.delete(cursor_position)
#Uses the eval function to calculate entered string in calculator.
def calculate_answer(self):
try:
#regex string that removes leading zeroes from the start of numbers and replaces the matched pattern with the numbers found in group 2.
answer = eval(re.sub(r"((?<=^)|(?<=[^\.\d]))0+(\d+)", r"\2", self.text_box.get()))
self.accepted_values.append(str(answer)) #appends answer to list of accepted values so that it is able to be inserted into the entry field through the validation algorithm.
self.text_box.delete(0, END) #deletes contents of entry field.
self.text_box.insert(0, answer)#inserts answer into entry field.
except (SyntaxError, ZeroDivisionError):#runs if a syntax error or zero division error is caught when calculating an answer.
messagebox.showwarning("Error", "Please edit your entered input and calculate again.\nCommon errors include:\n\n -Dividing by 0\n -Including too many decimal points in one number\n -Incorrect operator usage\n- Pressing equals when the screen is empty")
def validate_input(self, entered_value, modify_type, index):#used to validate input entered into entry field.
current_input = str(self.text_box.get())
index_pos = int(index)
#Cheecks if the attempted modification type is insertion or deletion.
if modify_type == '1': #insert
if entered_value== ".":
if current_input == "":
return True#allows a decimal point to be inserted if the field is blank.
elif index_pos == len(current_input):
if current_input[-1] == ".":
return False#doesn't allow a decimal to be inserted if the last character is a decimal.
else:
return True
elif current_input[index_pos-1] == "." or current_input[index_pos] =="." :
return False#doesn't allow a decimal to be inserted if there is a decimal point on either side of the insert position.
else:
return True
if entered_value in "*/+-":
if current_input == "" and entered_value in "*/":
return False#doesn't allow a multiplication or division operator to be entered into an empty field.
elif current_input == "" and entered_value in "+-":
return True#allows an addition or subtraction operator to be entered for negative and positive numbers
if index_pos == len(current_input):#if character is being inserted at the end of the string
if current_input[-1] in "+-" and entered_value in "+-":
return True#allows two addition or subtraction signs in a row.
elif current_input[-1] == "." and entered_value in "+-":
return False#doesn't allow the insertion of + or - after a decimal point.
elif current_input[-1] in "*/+-." and entered_value in "*/":
return False#doesn't allow * or / to be entered after */+-
else:
return True
if entered_value in "+-":
if current_input[index_pos-1] in "*/+-" or current_input[index_pos] in "*/+-" :
return True#allows a + or a - to be inserted after or before another operator.
elif current_input[index_pos-1] == ".":
return False#doesn't allow a + or a - to be entered after a decimal point.
elif entered_value in "*/":
if current_input[index_pos-1] in "*/+-." or current_input[index_pos] in "*/+-" :
return False#doesn't allow a * or / to be entered if there is an operator or decimal before, or an operator after.
else:
return True
#Checks if entered value is in list of accepted values, stored in setup of CalculatorGUI class.
if entered_value in self.accepted_values:
return True
#Accepts all attempts to remove text from the entryfield.
elif modify_type == "0":#delete
return True
return False
class CalculatorGUI(CalculatorFunctions):
def __init__(self, root):
self.root = root
#binds equals key to calculate an answer when pressed
root.bind("=", lambda event: self.calculate_answer())
#binds enter key to calculate an answer when pressed
root.bind('<Return>', lambda event: self.calculate_answer())
#list of values allowed to be inserted into entry field
self.accepted_values = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "-", "*", "/", "."]
self.create_number_buttons()
self.create_text_box()
self.create_special_buttons()
def create_number_buttons(self):
button_characters = "789*456/123-0.=+"
#Variable that is used to iterate through button characters.
i = 0
#Empty list to store created buttons in.
self.button_list = []
#Row starts at row 2 as I will have the entry field on row 0 and AC on row 1.
for row_counter in range(2,6):
#I want to have a 4x4 grid of buttons, so will use column in range 4 (0,1,2,3) for each row.
for column_counter in range(4):
#Appends each button to a list as it is created so that individual buttons are able to be referenced at later stages of program development.
self.button_list.append(Button(root, bg="#11708e", fg="white", pady=25, padx=35, text=button_characters[i], font=("Helvetica", 20, 'bold')))
self.button_list[i].grid(row=row_counter, column=column_counter, sticky="NSEW")
self.button_list[i].configure(command = lambda character=button_characters[i]: self.num_press(character))
i += 1
self.reconfigure_operator_buttons()
#Reconfigures operators ro have a red background.
def reconfigure_operator_buttons(self):
i = 0
for button in self.button_list:
#Cget gets the current value of the specified button attribute, in this case being "text".
if self.button_list[i].cget("text") in "-+/*.=":
self.button_list[i].configure(bg="#d14302")
if self.button_list[i].cget("text") == "=":
self.button_list[i].configure(command=self.calculate_answer)
i +=1
def create_text_box(self):
self.text_box = Entry(root, justify=RIGHT, validate="key", font=("Helvetica", 20, 'bold'), borderwidth=15)
self.text_box['validatecommand'] = (self.text_box.register(self.validate_input),'%S','%d', '%i')
#Places the entry field in row 0, column 0, adds internal padding to increase width of field.
self.text_box.grid(row=0, column=0, columnspan=4, ipady=10, sticky="WE")
def create_special_buttons(self):
clear_button = Button(root, bg="#302e2e", fg="white", text="AC", font=("Helvetica", 14, 'bold'), pady=10,command=self.clear_screen)
clear_button.grid(row=1, columnspan=2, sticky="WE")
backspace_button = Button(root, bg="#302e2e", fg="white", text="Backspace", font=("Helvetica", 14, 'bold'), pady=10, command=self.backspace)
backspace_button.grid(row=1, column=3, sticky="NSEW")
author_button = Button(root, bg="#302e2e", fg="white", font=("Helvetica", 14, 'bold'), text="Info", pady=10, command=self.show_info_popup)
author_button.grid(row=1, column=2, sticky="NSEW")
root = Tk()
root.title("Calculator")
root.resizable(0, 0)
calc = CalculatorGUI(root)
root.mainloop()