Mini Project : GUI Calculator using Python3 and tkinter

·

7 min read

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.

1_u56eov6oWIuRvQ1WsU7bPg.png

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()

1_eNmSJ4PuWzCFQOMqzOV2-Q.png

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.

1_rKumQme5lhTdR3Sa2IeL0A.png

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.

A video on how to code for this Mini Project is streaming on my YouTube Channel : Code Studio Sai Ankit . Do follow and stay tuned for many more interesting stuff on Software Development.