# Do the imports
import tkinter as tk
from decimal import Decimal
class Window:
def __init__(self, parent):
self.parent = parent
''' We will be using the grid manager '''
parent.columnconfigure(0, weight=1)
parent.rowconfigure(0, weight=1)
# Set a minimum size for our window
parent.geometry('800x600+200+200')
parent.resizable(False, False)
# Create a container to hold all the widgets.
# We will put the container in the parent widget
# and use sticky to pull it in all four directions.
# We also will use the padding argument to give some padding
# to the main container. The columnconfig and rowconfig helps
# in keeping the buttons width and heigh the same
container = tk.Frame(parent)
container.grid(column=0, row=0, sticky='news', padx=8, pady=6)
for i in range(4):
container.grid_columnconfigure(i, weight=3, uniform='cols')
for i in range(1, 6):
container.grid_rowconfigure(i, weight=3, uniform='rows')
# Create an entry widget and give it a column span of 4
self.entry = tk.Entry(container, font=(None, 16, 'normal'), relief='sunken')
self.entry.focus()
self.entry.grid(column=0, columnspan=4, row=0, sticky='new', pady=5, padx=4)
'''
Setting up the buttons we will create an empty list and
append the buttons as we create them. We will also set
variables alled col, row, and index. col and index will be set to 0
and row will be set to 1. We want our buttons to start at row 1.
The entry field is in row 0
'''
# Create the items that we want our button text to be
# The items list is setup to the order in which we build the buttons
# by row
items = [7,8,9,'*',5,6,7,'/',1,2,3,'+',0,'.','=','-']
self.btns = []
col, row, index = 0, 1, 0
for item in items:
size = 30 if item in ('.', '-') else 14
self.btns.append(tk.Button(container, text=item, cursor='hand2'))
self.btns[index]['font'] = (None, size, 'normal')
self.btns[index].grid(column=col, row=row, sticky='news', padx=4, pady=4)
index += 1
# If we reach the 3rd column - set col = 0 and increase row + 1
# else increase col + 1
if col >= 3:
col = 0
row +=1
else:
col += 1
# Create and append a clear button
self.btns.append(tk.Button(container, text='Clear', font=(None, 14, 'normal'), cursor='hand2'))
self.btns[16]['bg'] = 'tomato'
self.btns[16]['activebackground'] = 'red'
self.btns[16]['activeforeground'] = 'white'
self.btns[16].grid(column=0, columnspan=4, sticky='news', padx=4, pady=4)
class Controller:
''' The Controller class will hand all button commands as well as calculations '''
def __init__(self, window):
self.window = window
# Create commands for our buttons
for btn in self.window.btns:
# If the buttons are not = or clear - give the insert command
if btn['text'] not in ['Clear', '=']:
btn['command'] = lambda var=btn['text']: self.insert(var)
elif btn['text'] == 'Clear':
# If the button is the clear button - clear the field
btn['command'] = lambda: self.clear(None)
else:
# Else calculate
btn['command'] = lambda var=btn['text']: self.calculate(var)
# Bind keys to perform some actions
self.window.parent.bind('<Return>', lambda var=btn['text']: self.calculate(var))
self.window.parent.bind('<KP_Enter>', lambda var=btn['text']: self.calculate(var))
self.window.parent.bind('<Escape>', self.clear)
# Validate input - Needs to be numbers or *,+,/,-
valid = self.window.parent.register(self.valid_input)
self.window.entry.configure(validate='key', validatecommand=(valid, '%S'))
def valid_input(self,arg):
''' Method for validating input '''
if arg.isalpha() or arg == '=':
return False
else:
return True
def clear(self, event):
''' Method for clearing the entry field '''
self.window.entry.delete(0, 'end')
def insert(self, var):
''' Method for inserting numbers in the entry field '''
self.window.entry.insert('end', var)
def calculate(self, event):
''' Method for doing calculations '''
# Get the values of the entry field
values = self.window.entry.get()
# Do and get the calculations from the entry field using eval
# It is reccommended not to use eval but, for our purpose
# it will be fine
result = eval(values)
# Checking if our number is a float and is a whole number.
# If it is a whole number it will remove the .0
# else return the number as is
if isinstance(result, float):
result = Decimal(result).normalize()
else:
result = result
# Clear the entry field and insert the calculation back into the entry field
self.window.entry.delete(0, 'end')
self.window.entry.insert('end', result)
if __name__ == '__main__':
root = tk.Tk()
root.title('Tkinter Calculator')
controller = Controller(Window(root))
root.mainloop()