# How to Make a Calculator with Tkinter in Python

Learn how to make a calculator app with various features such as history and formulas using Tkinter library in Python.
10 min read · Updated aug 2022 · GUI Programming

Turn your code into any language with our Code Converter. It's the ultimate tool for multi-language programming. Start converting now!

## Idea

In this tutorial, we will make a calculator with Tkinter, the built-in GUI Library in Python. We are going to make a 3 by 3 Grid with buttons that represent the possible inputs, a live result showing system, a history of calculations feature, and variable insertion.

So let's get right into it. You can always get the complete code here.

## Imports

As always, we start with the imports. Because we make the UI with `tkinter`, we need to import it. We also import the `font` module from `tkinter` to change the fonts on our elements later.

We continue by getting the `partial()` function from `functools`, it is a genius function that excepts another function as a first argument and some `args` and `kwargs` and it will return a reference to this function with those arguments. This is especially useful when we want to insert one of our functions to a command argument of a button or a key binding.

In the following line, we import `ctypes`, which allows us to enable high dpi, making our window look way sharper. This is done with the function call in the last line of this section's code block.

Because we'll save our history in a JSON file, we import the `json` module to work with JSON files. We also need the built-in `re` module for our variable insertion feature.

Last but not least, we get the `math` module:

``````from tkinter import *
import tkinter.font as font
from functools import partial
import ctypes
import json
import re
# so the functions that can be used from the math module can be used in the line edit.
import math

ctypes.windll.shcore.SetProcessDpiAwareness(1)``````

## Variables and Tkinter setup

Next up, we make some variables, and we set up the `tkinter`:

``````# Colors
buttonColor = (255, 255, 255)
historyPanelBackground = (255, 255, 255)
# Tkinter Setup
root = Tk()
root.geometry("550x270")
root.title("Calculator")
# Setting icon for the Application
photo = PhotoImage(file = "icon.png")
root.iconphoto(False, photo)
myFont = font.Font(family='Consolas', size=12)``````

The first two variables (`buttonColor` and `historyPanelBackground`) are just colors for our buttons and the background of the history panel.

Next, we set up `tkinter` by calling its `Tk()` class and saving this object in the `root` variable. We then set the window dimensions with the `geometry()` method and the window title with the `title()`.

We then import an image from our directory (you can get the directory files here), which we can set as the icon of our program. After that, we import the Consolas font in size 12. We do this with the `Font()` class from the `font` module of `tkinter`.

## Formulas and Variable Insertions

Now I will explain the variable insertion feature, or at least I will try to! So the idea is that we can have a space after our equations where we declare variables inserted into the equation by placeholders. Let us look at this in a concrete example. If we type the text below into the line edit:

``{a} * {b} ? a=7 & b=3``

It should get us this result:

``21``

Because `a` will be placed by 7 and `b` by 3. Therefore, the equation will be evaluated to 21. We will later look at how this is done in practice.

Below we define a list of formulas that can be inserted into the line edit. We will make them pickable from a Menu:

``````# Formula Templates
formulas = [
['Pythagoras->c', '(({a}**2)+({b}**2))**0.5 ? a=5 & b=5'],
['Pythagoras->c**2', '({a}**2)+({b}**2) ? a=5 & b=5'],
['pq->(x1, x2)', '-({p}/2) + sqrt(({p}/2)**2 - ({q})), -({p}/2) - sqrt(({p}/2)**2 - ({q})) ? p=-1 & q=-12'],
['abc->(x1, x2)', 'quadratic_formula({a}, {b}, {c}) ? a=1 & b=5 & c=6'],
['Incline->y', '{m}*{x} + {q} ? m=4 & x=5 & q=6'],
]``````

## History Setup

Next, we set up the history feature. We start by declaring a list that will hold our `history` items. Then we have a variable that holds the location of the `history.json` file.

In the end, we have a try and except block, where there is an attempt to make the file at the specified location. This is just done, so the file exists in all cases.

``````# All the history equations are in this list.
history = []
# Where the history file is located.
historyFilePath = 'history.json'
# Creating History file if it does not exist.
try:
with open(historyFilePath, 'x') as fp:
pass
print("Created file at:", historyFilePath)
except:

## RGB to Hex and Math Function

Now we talk about two functions that hold only minor importance:

• `rgb_to_hex()` simply converts RGB colors to hex colors because `tkinter` only allows color names and hex colors.
• `quadratic_formula()` is a custom math function that can be used in the line edit.
``````# converting RGB values to HEX
def rgb_to_hex(rgb):
return "#%02x%02x%02x" % rgb

disc = b**2 - 4 * a * c
x1 = (-b - math.sqrt(disc)) / (2 * a)
x2 = (-b + math.sqrt(disc)) / (2 * a)
return (x1, x2)``````

## Helper Functions

Now we will go over the several helper functions needed to make the program work.

### The `addSymbol()` Function

This function will be called from the buttons to insert operators like `*` or `+` and numbers into the line edit. That's where the `symbol` parameter comes in. If the symbol is `<`, we won't append it; we will shorten the current input. We actually change the string variable that holds the current input.

``````# Add something to the current calculation
if symbol == '<':
entryVariable.set(entryVariable.get()[:-1])
else:
entryVariable.set(entryVariable.get()+symbol)``````

### The `varChange()` Function

This function will be connected to change events on the input variable. In this function, we will also evaluate the equation and insert it into the result label.

Earlier, we took a look at how the variable insertion feature functioned, and now we look at how we do this in practice:

``````def varChange(*args):
evaluationString = entryVariable.get().replace(' ', '').split('?')[0]
print('Before insertion: ',evaluationString)
if len(entryVariable.get().split('?')) == 2:
parameters = entryVariable.get().replace(' ', '').split('?')[1]
for param in parameters.split('&'):
where, what = param.split('=')
evaluationString = re.sub('{'+where+'}', what, evaluationString)
try:
print('After insertion: ', evaluationString)
resultLabel.config(text=str(eval(evaluationString)))
except:
resultLabel.config(text='Invalid Input')``````

As you see, we first split the input string by the `?` and then save this to the `evaluationString` variable.

After that, we check if the input string divided by `?` would result in a list with two items. If that's the case, we know that there are insertion variables. Then we get this side of the string, and we loop over another split version of this where the separator is `&`. There we modify the `evaluationString` with these variables.

We will try to insert the evaluated value into the result label in either case. It could be that this doesn't work because the input is invalid, so we cover that case too.

### The `saveCurrentInputToHistory()` Function

This function simply saves the current line edit input to the history file. We first check if the value is already in the list, so we don't have duplicates. We then save the `history` list to the history file. Here we use the `json.dump()` function:

``````def saveCurrentInputToHistory(event=None):
if entryVariable.get() in history:
return
history.append(entryVariable.get())
with open(historyFilePath, 'w') as file:
file.write(json.dumps(history))
updateListBox()``````

We also call the `updateListBox()` function, which will be explained in the next section.

### The `updateListBox()` Function

This function will delete all the contents of the history list and display them again. That's why we need the `history` variable here.

We delete all the elements in the list with the `delete(start, end)` method. Then we open the history file and get the JSON from there. In the end, we loop over the `history` list and insert those values into the `historyList`:

``````def updateListBox(event=None):
global history
historyList.delete(0, END)
try:
with open(historyFilePath, 'r') as file:
except json.decoder.JSONDecodeError:
print('File does not contain JSON')
for index, item in enumerate(history):
historyList.insert(index, item)``````

### The `setEntryFromHistory()` and `addFormula()` Functions

These two functions have simple jobs:

• The `setEntryFromHistory()` function enables us to click on a list item, and this item will then be inserted into the line edit.
• The `addFormula()` function will do the same just for the formulas chosen from the dropdown menu.
``````def setEntryFromHistory(event=None):
historyItem = historyList.get(historyList.curselection()[0])
entryVariable.set(historyItem)

saveCurrentInputToHistory()
entryVariable.set(formula)``````

## Making the UI

Now we will make the UI. I won't go into the detail too much. The layout is done with the `pack()` method of all widgets, and make the two-column setup by using `Frame`.

``````# Work with Frames to split the window in two parts: the calculator and the History Panel.
# Calculation Panel
calcSide = Frame(root)
calcSide.pack(side=LEFT, fill=BOTH, expand=1)
# Entry Variable for the calculations
entryVariable = StringVar(root, '4/2**2')
entryVariable.trace('w', varChange)

resultLabel = Label(calcSide, text='Result', font=myFont, borderwidth=0,anchor="e")
# History Panel
historySide = Frame(root, bg=rgb_to_hex(historyPanelBackground))
historySide.pack(side=LEFT, fill=BOTH, expand=1)

historyTopBar = Frame(historySide)
historyTopBar.pack(fill=X)
Label(historyTopBar, text='History').pack(side=LEFT)
Button(historyTopBar, text='Save Current Input', bg=rgb_to_hex(buttonColor), borderwidth=0, command=saveCurrentInputToHistory).pack(side=RIGHT)

historyList = Listbox(historySide, borderwidth=0)
historyList.pack(fill=BOTH, expand=True)
historyList.bind("<Double-Button-1>", setEntryFromHistory)``````

We also call this function, so the list updates on startup:

``````# Insert stuff into the history
updateListBox()``````

Below you see how the buttons are made. We first define a list with other lists where the symbols on the button are layed out.

Then we loop over this first list and make a new frame for each row, and we continue by looping over these inner lists and generating buttons with the given symbols.

We set the background color on these buttons to our button color, and then we lower every number from the button color tuple; this will give us a nice gradient for the buttons:

``````# Button Symbols (and their position)
symbols = [
['1', '2', '3', '+'],
['4', '5', '6', '-'],
['7', '8', '9', '/'],
['0', '.', '<', '*'],
]

for rowList in symbols:
# Make a row
row = Frame(calcSide)
row.pack(fill=BOTH, expand=True)
for symbol in rowList:
# Making and packing the Button
Button(
font=myFont, bg=rgb_to_hex(buttonColor), borderwidth=0) \
.pack(side=LEFT, fill=BOTH, expand=1)
# Change button color each iteration for gradient.
buttonColor = (buttonColor[0] - 10, buttonColor[1] - 10, buttonColor[1] - 2)``````

We make a menu where we have all our formulas ready to be inserted:

``````menubar = Menu(root)

for formula in formulas:

# Quit command

Finally, we call the `valueChange()` function so the input is evaluated on startup, and we call the `mainloop()` method:

``````# Call the var change once so it is evaluated without actual change.
varChange('foo')
root.mainloop()``````

## Showcase

Below you see a little showcase of how the calculator works:

## Conclusion

Excellent! You have successfully created a calculator using Python code! See how you can add more features to this program, such as more formulas or a converter for different things like inches to centimeters.

The complete code is on the full code page.

If you want to build more GUIs with Python, check our GUI programming tutorials page!

Happy coding ♥

Loved the article? You'll love our Code Converter even more! It's your secret weapon for effortless coding. Give it a whirl!

Sharing is caring!

### Comment panel

Got a coding query or need some guidance before you comment? Check out this Python Code Assistant for expert advice and handy tips. It's like having a coding tutor right in your fingertips!