Mini Project : GUI Calculator using Python3 and tkinter
By the end of this reading you would be able to code for a fully -functional Calculator that has all functionalities of a Calculator present on the side frame in macOS.
We are going to develop a Graphical User Interface version of calculator using Python3 and most famous GUI library tkinter. tkinter is Python’s de facto standard GUI and is included with the standard installs of Python3.
You can access the complete code here on GitHub
We shall discuss the process in three parts which are properly demarcated :
Part #1 : Prepare the application’s main window and get the app running
Import tkinter module into workspace and math module for using the SquareRoot function.
from tkinter import *
import math
Instantiate the Tk() class that provides us the ability to utilize the functionality of tkinter in our application.
root = Tk() #Instantiate and assign to a var 'root'
root.title('Calculator App') #Set the title of application window
Start the application’s main loop, waiting for mouse and keyboard events.
root.mainloop()
Code at the end of Step #1:
from tkinter import *
import math
root = Tk()
root.title('Calculator App')
root.mainloop()
After running the above python code snippet you will end up having a window open with title ‘Calculator App’
Part #2 : Building the calculator’s skeleton with no functionality
The mathematical expressions [numbers (or) operators] clicked by buttons would be visible on the answerEntryLabel.
The final answer on click of the “=” button after evaluating the expression would be visible on the answerFinalLabel.
AC ( All Clear ) button on click clears out the current data and prepares the calculator to start a new calculation.
The corresponding row and column numbers have been depicted in the wireframe to have a clear understanding of the placement of buttons and labels in our App.
Creating our Labels and the variables.
#answerEntryLabel
answerEntryLabel = StringVar()
Label(root, font=('futura', 25, 'bold'),
textvariable = answerEntryLabel,
justify = LEFT, height=2, width=7).grid(columnspan=4, ipadx=120)
#answerFinalLabel
answerFinalLabel = StringVar()
Label(root, font=('futura', 25, 'bold'),
textvariable = answerFinalLabel,
justify = LEFT, height=2, width=7).grid(columnspan = 4 , ipadx=120)
We can hardcode all the buttons but that doesn't actually prove a software developer’s niche towards development. So we are going to DRY (Don’t Repeat Yourself) our code by finding some relationship between widgets.
The buttons in the rows 2,3,4,5 and columns 0,1,2,3 are following a similar pattern of placement of the buttons. We will also create some blank buttons for the last row except the ‘.’ button.
We define a function that creates a button and the command that occurs when button is clicked is changeAnswerEntryLabel( ) which we shall code for later in the reading.
def createButton(txt,x,y):
Button(root, font=('futura', 15, 'bold'),
padx=16,pady=16,text = str(txt),
command = lambda:changeAnswerEntryLabel(txt),
height = 2, width=9).grid(row = x , column = y, sticky=E)
#buttons list stores the button values to be incoroporated in the calculator for first 4 rows
buttons = ['AC','√','%','/','7','8','9','*','4','5','6','-','1','2','3','+','','','.','']
#buttonsListTraversalCounter is used to traverse across the buttons list
buttonsListTraversalCounter = 0
#The for loop to create buttons for the application
for i in range(3,8):
for j in range(0,4):
createButton(buttons[buttonsListTraversalCounter],i,j)
buttonsListTraversalCounter =buttonsListTraversalCounter + 1
We will hardcode all the other buttons because either they have different commands or have different placements in the grid.
#Button for SquareRoot
Button(root,font=('futura', 15, 'bold'),
padx=16,pady=16, text = "√",
command = lambda:evaluateSquareRoot(),
height=2, width=9).grid(row = 3 , column = 1, sticky = E)
#Button for AC button - clear the workspace
Button(root,font=('futura', 15, 'bold'),
padx=16,pady=16, text = "AC",
command = lambda:allClear(),
height=2, width=9).grid(row = 3 , column = 0 , sticky = E)
#Button for value 0 - have 2 columnspace and different dimensions
Button(root,font=('futura', 15, 'bold'),
padx=16,pady=16, text = "0",
command = lambda:changeAnswerEntryLabel(0),
height=2, width=21).grid(row = 7 , column = 0 ,
columnspan=2 , sticky = E)
#Button for "=" - final calc button
Button(root,font=('futura', 15, 'bold'),
padx=16,pady=16, text = "=",
command = lambda:evaluateAnswer(),
height=2, width=9).grid(row = 7 , column = 3, sticky = E)
Running the above script would obviously give you an error because all the commands on click of buttons are not yet declared, but actually this creates the main skeleton of the application.
Part #3 : Adding the functionality to the calculator app
To store the expression we are entering with the help of buttons that should also be printed on the answerEntryLabel is stored in a global string variable. Let us also create another global string variable whose use will be discussed later.
answerVariableGlobal = ""
answerLabelForSquareRoot = ""
Let us create the function changeAnswerEntryLabel( ) that appends the entry by buttons to answerEntryLabel on the click.
def changeAnswerEntryLabel(entry):
global answerVariableGlobal
global answerLabelForSquareRoot
answerVariableGlobal = answerVariableGlobal + str(entry)
answerLabelForSquareRoot = answerVariableGlobal
answerEntryLabel.set(answerVariableGlobal)
Let us create the function allClear() that works on click on “AC” button.
def allClear():
global answerVariableGlobal
global answerLabelForSquareRoot
answerVariableGlobal = ""
answerLabelForSquareRoot = ""
answerEntryLabel.set("")
answerFinalLabel.set("")
Let us create the function clearAnswerEntryLabel. We shall use this function later called to clear answerEntryLabel and also clear answerVariableGlobal. We have created the answerLabelForSquareRoot because once the “=” button is clicked the value in the answerVariableGlobal is evaluated and cleared using this function but answerLabelForSquareRoot shouldn’t be erased and contain the expression so that we can evaluate the SquareRoot of the answer when “√” is clicked.
def clearAnswerEntryLabel():
global answerVariableGlobal
global answerLabelForSquareRoot
answerLabelForSquareRoot = answerVariableGlobal
answerVariableGlobal = ""
answerEntryLabel.set(answerVariableGlobal)
Let us create the main function “evaluateAnswer( )” that evaluates the expression present in the answerVariableGlobal and returns the value to answerFinalLabel also clearing the answerEntryLabel using clearAnswerEntryLabel( )
def evaluateAnswer():
global answerVariableGlobal
try:
eval(answerVariableGlobal)
evaluatedValueAnswerLabelGlobal= str(eval(answerVariableGlobal)
clearAnswerEntryLabel()
answerFinalLabel.set(evaluatedValueAnswerLabelGlobal)
#Here we are handling the error when the expression has some SyntaxError or for that matter any Error.
except(ValueError,SyntaxError,TypeError, ZeroDivisionError):
clearAnswerEntryLabel()
answerFinalLabel.set("Error!")
Let us create the final function evaluateSquareRoot( ) that evaluates the expression present in the answerLabelForSquareRoot for square root of that value and returns that value to answerFinalLabel.
def evaluateSquareRoot():
global answerVariableGlobal
global answerLabelForSquareRoot
try:
sqrtAnswer = math.sqrt(eval(str(answerLabelForSquareRoot)))
clearAnswerEntryLabel()
answerFinalLabel.set(sqrtAnswer)
except(ValueError,SyntaxError,TypeError, ZeroDivisionError):
try:
sqrtAnswer = math.sqrt(eval(str(answerVariableGlobal)))
clearAnswerEntryLabel()
answerFinalLabel.set(sqrtAnswer)
#Error Handling
except(ValueError,SyntaxError,
TypeError,ZeroDivisionError):
clearAnswerEntryLabel()
answerFinalLabel.set("Error!")
The complete final code by assembling all the three parts we get :
from tkinter import *
import math
answerVariableGlobal = ""
answerLabelForSquareRoot = ""
root = Tk()
root.title('Calculator App')
answerEntryLabel = StringVar()
Label(root, font=('futura', 25, 'bold'),
textvariable = answerEntryLabel,
justify = LEFT, height=2, width=7).grid(columnspan=4, ipadx=120)
answerFinalLabel = StringVar()
Label(root, font=('futura', 25, 'bold'),
textvariable = answerFinalLabel,
justify = LEFT, height=2, width=7).grid(columnspan = 4 , ipadx=120)
def changeAnswerEntryLabel(entry):
global answerVariableGlobal
global answerLabelForSquareRoot
answerVariableGlobal = answerVariableGlobal + str(entry)
answerLabelForSquareRoot = answerVariableGlobal
answerEntryLabel.set(answerVariableGlobal)
def clearAnswerEntryLabel():
global answerVariableGlobal
global answerLabelForSquareRoot
answerLabelForSquareRoot = answerVariableGlobal
answerVariableGlobal = ""
answerEntryLabel.set(answerVariableGlobal)
def evaluateSquareRoot():
global answerVariableGlobal
global answerLabelForSquareRoot
try:
sqrtAnswer = math.sqrt(eval(str(answerLabelForSquareRoot)))
clearAnswerEntryLabel()
answerFinalLabel.set(sqrtAnswer)
except(ValueError,SyntaxError,TypeError, ZeroDivisionError):
try:
sqrtAnswer = math.sqrt(eval(str(answerVariableGlobal)))
clearAnswerEntryLabel()
answerFinalLabel.set(sqrtAnswer)
except(ValueError,SyntaxError,TypeError,ZeroDivisionError):
clearAnswerEntryLabel()
answerFinalLabel.set("Error!")
def evaluateAnswer():
global answerVariableGlobal
try:
eval(answerVariableGlobal)
evaluatedValueAnswerLabelGlobal= str(eval(answerVariableGlobal) # This line should be alligned properly without any indentation error
clearAnswerEntryLabel()
answerFinalLabel.set(evaluatedValueAnswerLabelGlobal)
except(ValueError,SyntaxError,TypeError, ZeroDivisionError):
clearAnswerEntryLabel()
answerFinalLabel.set("Error!")
def allClear():
global answerVariableGlobal
global answerLabelForSquareRoot
answerVariableGlobal = ""
answerLabelForSquareRoot = ""
answerEntryLabel.set("")
answerFinalLabel.set("")
def createButton(txt,x,y):
Button(root, font=('futura', 15, 'bold'),
padx=16,pady=16,text = str(txt),
command = lambda:changeAnswerEntryLabel(txt),
height = 2, width=9).grid(row = x , column = y, sticky=E)
buttons = ['AC','√','%','/','7','8','9','*','4','5','6','-','1','2','3','+','','','.','']
buttonsListTraversalCounter = 0
for i in range(3,8):
for j in range(0,4):
createButton(buttons[buttonsListTraversalCounter],i,j)
buttonsListTraversalCounter =buttonsListTraversalCounter + 1
Button(root,font=('futura', 15, 'bold'),
padx=16,pady=16, text = "√",
command = lambda:evaluateSquareRoot(),
height=2, width=9).grid(row = 3 , column = 1, sticky = E)
Button(root,font=('futura', 15, 'bold'),
padx=16,pady=16, text = "AC",
command = lambda:allClear(),
height=2, width=9).grid(row = 3 , column = 0 , sticky = E)
Button(root,font=('futura', 15, 'bold'),
padx=16,pady=16, text = "0",
command = lambda:changeAnswerEntryLabel(0),
height=2, width=21).grid(row = 7 , column = 0 ,
columnspan=2 , sticky = E)
Button(root,font=('futura', 15, 'bold'),
padx=16,pady=16, text = "=",
command = lambda:evaluateAnswer(),
height=2, width=9).grid(row = 7 , column = 3, sticky = E)
root.mainloop()
Looks Easy !! Isn’t it ?
This is how you can develop a Mini Project using Python3.