Building An OOP Calculator And What It Means To Write A Widget Library
Tags: python, oop, tkinter, showdev
Published: 2019-02-17 13:26:10

---
title: Building An OOP Calculator And What It Means To Write A Widget Library
published: true
description: when oop opens the door to a new beginning
tags: python, oop, tkinter, showdev
cover_image: https://i.ibb.co/PwHgBWf/rawpixel-602154-unsplash.jpg
---
In this post we are going to code an OOP calculator, actually making it a widget for easy plug and play. Python's flexibility and oop make a nice match!

## Our Skeleton

We are using tkinter (actually once again, it's pronounced tee-kay-inter) and widget guys means window gadget

```python
from tkinter import *


class App:
    def __init__(self, master):
        self.master = master


root = Tk()
app = App(root)
root.title('dev.to calculator')
root.mainloop()

```
we get

![alt text](https://thepracticaldev.s3.amazonaws.com/i/3tilnf8lf00c5d2p1hkf.png)

and as usual, that's some info

![alt text](https://thepracticaldev.s3.amazonaws.com/i/77oogo8o0btm3vb9fwfe.png)

## Our Calculator

here is a normal class

```python
class Calculator:
    def __init__(self, parent, x, y):
        pass

```
we add a frame to hold all our elements so that we just plug in a frame

```python
class Calculator:
    def __init__(self, parent, x, y):
        self.parent = parent

        self.container = Frame(self.parent)
        self.container.grid(row=x, column=y)

```
we can now add our calculator to our app

```python
class App:
    def __init__(self, master):
        self.master = master

        calc = Calculator(self.master, 0, 0)

```
we get 

![alt text](https://thepracticaldev.s3.amazonaws.com/i/q4n7uk9lvwek9z86k4dk.png)

## Some Initialisations In our Calculator

```python
class Calculator:
    def __init__(self, parent, x, y):
        self.button_font = ('Verdana', 15)
        self.entry_font = ('Verdana', 20)
        self.parent = parent

        self.button_width = 4
        self.button_height = 1
        self.container = Frame(self.parent)
        self.container.grid(row=x, column=y)

        self.string = ''

```

those two are just font name and font size
self.button_font = ('Verdana', 15)
self.entry_font = ('Verdana', 20)

a frame can be thought of as a container

in self.string we'll be storing what the what we'll be displaying on our calculator screen

## A Note On Grid

grid tells us where to place our elements, the coordinates start from 0, 0

## Elegantly Adding Elements

instead of each time adding two lines of code for one button, we'll add a simple line by defining this in the calculator class

```python
def button(self, char_, x_, y_):
        self.b = Button(
            self.container, text=char_, width=self.button_width,
            height=self.button_height, font=self.entry_font)
        self.b.grid(row=x_, column=y_)
```
and using it like that
```python
self.button('7', 1, 0)
```
same for our text field we'd be displaying our characters

```python
def entry(self, x_, y_):
        self.entry = Text(
            self.container, font=self.entry_font, state=DISABLED,
            height=self.button_height//2, width=self.button_width*5)
        self.entry.grid(row=x_, column=y_, columnspan=5, sticky='we')
```
usage
```python
self.entry(0, 0)
```
state=DISABLED is used so that we can't write anything by placing our cursor
columnspan expands our element in other columns

## Actually adding them

in our calculator class

```python
        self.entry(0, 0)

        self.button('7', 1, 0)
        self.button('8', 1, 1)
        self.button('9', 1, 2)

        self.button('4', 2, 0)
        self.button('5', 2, 1)
        self.button('6', 2, 2)

        self.button('1', 3, 0)
        self.button('2', 3, 1)
        self.button('3', 3, 2)

        self.button('0', 4, 0)

        self.button('+', 1, 3)
        self.button('-', 1, 4)
        self.button('*', 2, 3)
        self.button('/', 2, 4)

        self.button('(', 3, 3)
        self.button(')', 3, 4)
```
here is what we get

![alt text](https://thepracticaldev.s3.amazonaws.com/i/gjo1wz2bw07titqsc2g6.png)

## Adding the remove one char, equal and clear buttons

we'll have three buttons, one to remove a char if we mistakenly added one
one to evaluate our result and one to clear the display completely

the evaluate button

```python
def button_eq(self, char_, x_, y_):
        self.b = Button(
            self.container, text=char_, width=self.button_width,
            height=self.button_height, font=self.entry_font)
        self.b.grid(row=x_, column=y_, sticky='we', columnspan=2)
```
usage
```python
self.button_eq('=', 4, 1)
```

the remove one char button
```python
def button_rem(self, char_, x_, y_):
        self.b = Button(
            self.container, text=char_, width=self.button_width,
            height=self.button_height, font=self.entry_font)
        self.b.grid(row=x_, column=y_)
```
usage

```python
self.button_rem('<', 4, 4)
```
the clear button
```python
def button_clear(self, char_, x_, y_):
        self.b = Button(
            self.container, text=char_, width=self.button_width,
            height=self.button_height, font=self.entry_font)
        self.b.grid(row=x_, column=y_)
```
usage

```python
self.button_clear('clear', 4, 3)
```
## Where we are

code upto now
```python
from tkinter import *

class Calculator:
    def __init__(self, parent, x, y):
        self.button_font = ('Verdana', 15)
        self.entry_font = ('Verdana', 20)
        self.parent = parent

        self.button_width = 4
        self.button_height = 1
        self.container = Frame(self.parent)
        self.container.grid(row=x, column=y)

        self.string = ''

        self.entry(0, 0)

        self.button('7', 1, 0)
        self.button('8', 1, 1)
        self.button('9', 1, 2)

        self.button('4', 2, 0)
        self.button('5', 2, 1)
        self.button('6', 2, 2)

        self.button('1', 3, 0)
        self.button('2', 3, 1)
        self.button('3', 3, 2)

        self.button('0', 4, 0)

        self.button('+', 1, 3)
        self.button('-', 1, 4)
        self.button('*', 2, 3)
        self.button('/', 2, 4)

        self.button('(', 3, 3)
        self.button(')', 3, 4)

        self.button_eq('=', 4, 1)

        self.button_clear('clear', 4, 3)
        self.button_rem('<', 4, 4)

    def button_eq(self, char_, x_, y_):
        self.b = Button(
            self.container, text=char_, width=self.button_width,
            height=self.button_height, font=self.entry_font)
        self.b.grid(row=x_, column=y_, sticky='we', columnspan=2)

    def button_rem(self, char_, x_, y_):
        self.b = Button(
            self.container, text=char_, width=self.button_width,
            height=self.button_height, font=self.entry_font)
        self.b.grid(row=x_, column=y_)

    def button_clear(self, char_, x_, y_):
        self.b = Button(
            self.container, text=char_, width=self.button_width,
            height=self.button_height, font=self.entry_font)
        self.b.grid(row=x_, column=y_)

    def button(self, char_, x_, y_):
        self.b = Button(
            self.container, text=char_, width=self.button_width,
            height=self.button_height, font=self.entry_font)
        self.b.grid(row=x_, column=y_)

    def entry(self, x_, y_):
        self.entry = Text(
            self.container, font=self.entry_font, state=DISABLED,
            height=self.button_height//2, width=self.button_width*5)
        self.entry.grid(row=x_, column=y_, columnspan=5, sticky='we')


class App:
    def __init__(self, master):
        self.master = master

        calc = Calculator(self.master, 0, 0)


root = Tk()
app = App(root)
root.title('dev.to calculator')
root.mainloop()
```
our gui

![alt text](https://thepracticaldev.s3.amazonaws.com/i/ai641bhvcs9kdjixexik.png)

## Adding The Logic

displaying the text makes the textarea normal, adds then disables it again

```python
    def display(self, text_):
        self.entry.config(state=NORMAL)
        self.entry.delete('1.0', END)
        self.entry.insert('1.0', text_)
        self.entry.config(state=DISABLED)
    
```
clicking on any button adds the char on the button to the display

```python
    def normal_button_click(self, text_):
        self.string = '' + self.string + text_
        self.display(self.string)
```
pressing the equal button evaluated whatever we're writing. we are using py's eval, so we can use ** for exponential

```python
    def equal_button_click(self):
        self.display(eval(self.string))
        self.string = ''   
```
removing one char just reduces the string by excluding the last char

```python
    def rem_button_click(self):
        self.string = '' + self.string[0:-1]
        self.display(self.string)
```
clear buttons resets all

```python
    def clear_button_click(self):
        self.display('')
        self.string = ''
```
## Our Buttons With Added Commands

a normal command is added by 

command = function_name

but a command with parameters is added by

command=lambda: self.function(parameter_)

```python
    def button(self, char_, x_, y_):
        self.b = Button(
            self.container, text=char_, width=self.button_width,
            height=self.button_height, font=self.entry_font,
            command=lambda: self.normal_button_click(char_))
        self.b.grid(row=x_, column=y_)

    def button_eq(self, char_, x_, y_):
        self.b = Button(
            self.container, text=char_, width=self.button_width,
            height=self.button_height, font=self.entry_font,
            command=self.equal_button_click)
        self.b.grid(row=x_, column=y_, sticky='we', columnspan=2)

    def button_rem(self, char_, x_, y_):
        self.b = Button(
            self.container, text=char_, width=self.button_width,
            height=self.button_height, font=self.entry_font,
            command=self.rem_button_click)
        self.b.grid(row=x_, column=y_)

    def button_clear(self, char_, x_, y_):
        self.b = Button(
            self.container, text=char_, width=self.button_width,
            height=self.button_height, font=self.entry_font,
            command=self.clear_button_click)
        self.b.grid(row=x_, column=y_)
```
## Finally It

our complete code

```python
from tkinter import *


class Calculator:
    def __init__(self, parent, x, y):
        self.button_font = ('Verdana', 15)
        self.entry_font = ('Verdana', 20)
        self.parent = parent

        self.button_width = 4
        self.button_height = 1
        self.container = Frame(self.parent)
        self.container.grid(row=x, column=y)

        self.string = ''

        self.entry(0, 0)

        self.button('7', 1, 0)
        self.button('8', 1, 1)
        self.button('9', 1, 2)

        self.button('4', 2, 0)
        self.button('5', 2, 1)
        self.button('6', 2, 2)

        self.button('1', 3, 0)
        self.button('2', 3, 1)
        self.button('3', 3, 2)

        self.button('0', 4, 0)

        self.button('+', 1, 3)
        self.button('-', 1, 4)
        self.button('*', 2, 3)
        self.button('/', 2, 4)

        self.button('(', 3, 3)
        self.button(')', 3, 4)

        self.button_eq('=', 4, 1)

        self.button_clear('clear', 4, 3)
        self.button_rem('<', 4, 4)

    def entry(self, x_, y_):
        self.entry = Text(
            self.container, font=self.entry_font, state=DISABLED,
            height=self.button_height//2, width=self.button_width*5)
        self.entry.grid(row=x_, column=y_, columnspan=5, sticky='we')

    def button(self, char_, x_, y_):
        self.b = Button(
            self.container, text=char_, width=self.button_width,
            height=self.button_height, font=self.entry_font,
            command=lambda: self.normal_button_click(char_))
        self.b.grid(row=x_, column=y_)

    def button_eq(self, char_, x_, y_):
        self.b = Button(
            self.container, text=char_, width=self.button_width,
            height=self.button_height, font=self.entry_font,
            command=self.equal_button_click)
        self.b.grid(row=x_, column=y_, sticky='we', columnspan=2)

    def button_rem(self, char_, x_, y_):
        self.b = Button(
            self.container, text=char_, width=self.button_width,
            height=self.button_height, font=self.entry_font,
            command=self.rem_button_click)
        self.b.grid(row=x_, column=y_)

    def button_clear(self, char_, x_, y_):
        self.b = Button(
            self.container, text=char_, width=self.button_width,
            height=self.button_height, font=self.entry_font,
            command=self.clear_button_click)
        self.b.grid(row=x_, column=y_)

    def display(self, text_):
        self.entry.config(state=NORMAL)
        self.entry.delete('1.0', END)
        self.entry.insert('1.0', text_)
        self.entry.config(state=DISABLED)

    def normal_button_click(self, text_):
        self.string = '' + self.string + text_
        self.display(self.string)

    def equal_button_click(self):
        self.display(eval(self.string))
        self.string = ''

    def rem_button_click(self):
        self.string = '' + self.string[0:-1]
        self.display(self.string)

    def clear_button_click(self):
        self.display('')
        self.string = ''


class App:
    def __init__(self, master):
        self.master = master

        calc = Calculator(self.master, 0, 0)


root = Tk()
app = App(root)
root.title('dev.to calculator')
root.mainloop()
```
![alt text](https://thepracticaldev.s3.amazonaws.com/i/nos9pyme4ojhu9rbakd1.gif)
## Exploring Widget Flexibility

Since our calculator is a widget, we can add mooore

```python
        calc = Calculator(self.master, 0, 0)
        calc = Calculator(self.master, 0, 1)
        calc = Calculator(self.master, 1, 0)
        calc = Calculator(self.master, 1, 1)

```
gives us

![alt text](https://thepracticaldev.s3.amazonaws.com/i/tms70qddw6vddlnxuje0.png)

on a single screen, each functioning as an app on it's own

## So ... A Widget Library

A widget library is just some gui classes. Easy isn't it?

cover img credit: rawpixels on unsplash