6
\$\begingroup\$

This is my python program for a GUI Calculator made using the tkinter module. This took me about 2 days to complete. However, I still believe that there is room for improvement. I am missing a few other buttons, and I do plan to add them later on. Also, I was unsure whether I should use the default style of tkinter or to use the ttk style (which looks better in my opinion). Nonetheless, I used to the default style just to test it out anyway, but is this style better or the ttk style better? Please give me any tips and advice you can on this topic.

# Calculator made using pythons tkinter module
# Author - Mohamed Akil

from tkinter import *

class Application(Frame):
    """ Main class for calculator"""

    def __init__(self, master):
        """ Initialise the Frame. """
        super(Application, self).__init__(master)
        self.task = ""
        self.UserIn = StringVar()
        self.grid()
        self.create_widgets()

    def create_widgets(self):
        """ Create all the buttons for calculator. """
        # User input stored as an Entry widget.

        self.user_input = Entry(self, bg = "#5BC8AC", bd = 29, 
        insertwidth = 4, width = 24,
        font = ("Verdana", 20, "bold"), textvariable = self.UserIn, justify = RIGHT)
        self.user_input.grid(columnspan = 4)

        self.user_input.insert(0, "0")

        # Button for value 7
        self.button1 = Button(self, bg = "#98DBC6", bd = 12,
        text = "7", padx = 33, pady = 25, font = ("Helvetica", 20, "bold"), 
        command = lambda : self.buttonClick(7))
        self.button1.grid(row = 2, column = 0, sticky = W)

        # Button for value 8
        self.button2 = Button(self, bg = "#98DBC6", bd = 12, 
        text = "8",  padx = 35, pady = 25, 
        command = lambda : self.buttonClick(8), font = ("Helvetica", 20, "bold"))
        self.button2.grid(row = 2, column = 1, sticky = W)

        # Button for value 9
        self.button3 = Button(self, bg = "#98DBC6", bd = 12, 
        text = "9",  padx = 33, pady = 25,
        command = lambda : self.buttonClick(9), font = ("Helvetica", 20, "bold"))
        self.button3.grid(row = 2, column = 2, sticky = W)

        # Button for value 4
        self.button4 = Button(self, bg = "#98DBC6", bd = 12,
        text = "4",  padx = 33, pady = 25,
        command = lambda : self.buttonClick(4), font = ("Helvetica", 20, "bold"))
        self.button4.grid(row = 3, column = 0, sticky = W)

        # Button for value 5
        self.button5 = Button(self, bg = "#98DBC6", bd = 12, 
        text = "5",  padx = 35, pady = 25,
        command = lambda : self.buttonClick(5), font = ("Helvetica", 20, "bold"))
        self.button5.grid(row = 3, column = 1, sticky = W)

        # Button for value 6
        self.button6 = Button(self, bg = "#98DBC6", bd = 12, 
        text = "6",  padx = 33, pady = 25,
        command = lambda : self.buttonClick(6), font = ("Helvetica", 20, "bold"))
        self.button6.grid(row = 3, column = 2, sticky = W)

        # Button for value 1
        self.button7 = Button(self, bg = "#98DBC6", bd = 12, 
        text = "1",  padx = 33, pady = 25, 
        command = lambda : self.buttonClick(1), font = ("Helvetica", 20, "bold"))
        self.button7.grid(row = 4, column = 0, sticky = W)

        # Button for value 2
        self.button8 = Button(self, bg = "#98DBC6", bd = 12, 
        text = "2",  padx = 35, pady = 25,
        command = lambda : self.buttonClick(2), font = ("Helvetica", 20, "bold"))
        self.button8.grid(row = 4, column = 1, sticky = W)

        # Button for value 3
        self.button9 = Button(self, bg = "#98DBC6", bd = 12, 
        text = "3",  padx = 33, pady = 25,
        command = lambda : self.buttonClick(3), font = ("Helvetica", 20, "bold"))
        self.button9.grid(row = 4, column = 2, sticky = W)

        # Button for value 0
        self.button9 = Button(self, bg = "#98DBC6", bd = 12, 
        text = "0",  padx = 33, pady = 25,
        command = lambda : self.buttonClick(0), font = ("Helvetica", 20, "bold"))
        self.button9.grid(row = 5, column = 0, sticky = W)

        # Operator buttons
        # Addition button
        self.Addbutton = Button(self, bg = "#98DBC6", bd = 12, 
        text = "+",  padx = 36, pady = 25,
        command = lambda : self.buttonClick("+"), font = ("Helvetica", 20, "bold"))
        self.Addbutton.grid(row = 2, column = 3, sticky = W)

        # Subtraction button
        self.Subbutton = Button(self, bg = "#98DBC6", bd = 12, 
        text = "-",  padx = 39, pady = 25,
        command = lambda : self.buttonClick("-"), font = ("Helvetica", 20, "bold"))
        self.Subbutton.grid(row = 3, column = 3, sticky = W)

        # Multiplication button
        self.Multbutton = Button(self, bg = "#98DBC6", bd = 12, 
        text = "*",  padx = 38, pady = 25,
        command = lambda : self.buttonClick("*"), font = ("Helvetica", 20, "bold"))
        self.Multbutton.grid(row = 4, column = 3, sticky = W)

        # Division button
        self.Divbutton = Button(self, bg = "#98DBC6", bd = 12, 
        text = "/",  padx = 39, pady = 25,
        command = lambda : self.buttonClick("/"), font = ("Helvetica", 20, "bold"))
        self.Divbutton.grid(row = 5, column = 3, sticky = W)

        # Equal button
        self.Equalbutton = Button(self, bg = "#E6D72A", bd = 12, 
        text = "=",  padx = 100, pady = 25,
        command = self.CalculateTask, font = ("Helvetica", 20, "bold"))
        self.Equalbutton.grid(row = 5, column = 1, sticky = W, columnspan = 2)

        # Clear Button
        self.Clearbutton = Button(self, bg = "#E6D72A", bd = 12,
        text = "AC", font = ("Helvetica", 20, "bold"), width = 28, padx = 7, command = self.ClearDisplay)
        self.Clearbutton.grid(row = 1, columnspan = 4, sticky = W)

    def buttonClick(self, number):
        self.task = str(self.task) + str(number)
        self.UserIn.set(self.task)

    def CalculateTask(self):
        self.data = self.user_input.get()
        try:
            self.answer = eval(self.data)
            self.displayText(self.answer)
            self.task = self.answer

        except SyntaxError as e:
            self.displayText("Invalid Syntax!")
            self.task = ""

    def displayText(self, value):
        self.user_input.delete(0, END)
        self.user_input.insert(0, value)

    def ClearDisplay(self):
        self.task = ""
        self.user_input.delete(0, END)
        self.user_input.insert(0, "0")


calculator = Tk()

calculator.title("Calculator")
app = Application(calculator)
# Make window fixed (cannot be resized)
calculator.resizable(width = False, height = False)

calculator.mainloop()
\$\endgroup\$
  • 3
    \$\begingroup\$ I don't really like that you're using eval. That evaluates Python code, not just mathematical expressions. For example, a trivial __import__('code').interact(local=dict(locals(), **globals())) in your input box gives the user an interactive Python console that can manipulate your code to do whatever he wants. It also has access to all of your variables, so yes it is more powerful than a normal Python console. \$\endgroup\$ – zondo Apr 23 '17 at 20:44
  • \$\begingroup\$ Thanks for the advice! I will make necessary changes to the program... @zondo \$\endgroup\$ – Akil Riaz Apr 24 '17 at 2:22
2
\$\begingroup\$

Ideally, you should have at least 2 classes: one to hold functions performing different actions on the GUI, and the other class to draw the GUI itself. Below, I provide suggestions to improve the second class ONLY because, for a performant calculator, there are much more things to improve.

Avoid wildcard imports

You should use import tkinter as tk instead of from tkinter import *. I know most tutorials teach the opposite, but that is a bad option because it can lead to namespaces problems.

Code duplication

The way you created the different buttons lead you to duplicate code several times. You need to find a way to refactor your code.

My approach to avoid that is to create all the buttons within a sing function:

def create_calculator_buttons_texts(self):
       button_column = 0
       button_row = 1
       text_buttons = ('A', '789+', '456-', '123*', '0=/')
       for button_rows in text_buttons:
           for text_button in button_rows:               
               self.configure_and_place_button(text_button, button_row, button_column)
               button_column += 1
           button_row += 1
           button_column = 0

Where configure_and_place_button() is the function that decorates the buttons:

def configure_and_place_button(self, text_button, button_row, button_column):
       key = list(self.calculator_buttons_texts.keys())[list(self.calculator_buttons_texts.values()).index(text_button)]
       self.calculator_buttons_texts[key] = tk.Button(self.master, text=text_button, bg="#5BC8AC", bd=12)
       self.calculator_buttons_texts[key].config(padx=33, pady=25, font=("Helvetica", 20, "bold"))
       self.calculator_buttons_texts[key].grid(row=button_row,column=button_column)
       self.reconfigure_specific_buttons(key, text_button)

Because all the buttons are designed the same way, we need to rely on an other function to to style the /, AC and = buttons:

def reconfigure_specific_buttons(self, key, text_button):
       self.reconfigure_clear_button(key, text_button)
       self.reconfigure_equals_button(key, text_button)     
       self.reconfigure_divide_button(key, text_button)

Stick the clear button to East and West

For the moment, there is an ugly empty space on the right of AC button: enter image description here

This happened because you used sticky = W. To remedy to this problem, use sticky=tk.W+tk.E. This will result in:

enter image description here

Avoid spaces when coding tk.Class(options)'s operators

While spacing is a good practice, especially in Python, it should be avoided when writing a tkinter class/function options' operators (+ and =). I mean, something like this:

self.Equalbutton.grid(row = 5, column = 1, sticky = W, columnspan = 2)

Should be written this way:

self.Equalbutton.grid(row=5, column=1, sticky=W, columnspan=2)

Respect the naming conventions

I suggest you to take a look at PEP 8 to read about the naming conventions for variables and functions in Python. For example, the self.Equalbutton() just menioned above, should be written self.equal_button().

Improved GUI class code

Here is a Calculator_GUI() class. As I said in the opening paragraph, I am not incorporating any functionality to this GUI. You should create a different class to handle functions performing the different operations.

First, I create a module to save in some global ugly data:

calculator_buttons_texts ={
   'clear':'A',
   'zero':'0',
   'one':'1',
   'two':'2',
   'three':'3',
   'four':'4',
   'five':'5',
   'six':'6',
   'seven':'7',
   'eight':'8',
   'nine':'9',
   'plus':'+',
   'minus':'-',
   'multiply':'*',
   'divide':'/',
   'equal':'='           
}   

It is better to keep this dirty data in a separate module in order not to pollute your class. This will also allow you to add addition buttons in the future if needed.

Here is the GUI class:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import calculator_data    
import tkinter as tk


class Calculator_Core:
   def __init__(self):
       # Do this yourself
       pass

class Calculator(tk.Frame):

   def __init__(self, master):
       tk.Frame.__init__(self, master)
       self.master = master 
       self.calculator_buttons_texts = calculator_data.calculator_buttons_texts

       self.configure_gui() 
       self.create_calculator_widgets()      


   def configure_gui(self):
       self.master.title('Calculator')
       self.master.resizable(False, False)

   def create_calculator_widgets(self):
       self.create_calculator_input_field()
       self.create_calculator_buttons_texts()

   def create_calculator_buttons_texts(self):
       button_column = 0
       button_row = 1
       text_buttons = ('A', '789+', '456-', '123*', '0=/')
       for button_rows in text_buttons:
           for text_button in button_rows:               
               self.configure_and_place_button(text_button, button_row, button_column)
               button_column += 1
           button_row += 1
           button_column = 0

   def configure_and_place_button(self, text_button, button_row, button_column):
       key = list(self.calculator_buttons_texts.keys())[list(self.calculator_buttons_texts.values()).index(text_button)]
       self.calculator_buttons_texts[key] = tk.Button(self.master, text=text_button, bg="#5BC8AC", bd=12)
       self.calculator_buttons_texts[key].config(padx=33, pady=25, font=("Helvetica", 20, "bold"))
       self.calculator_buttons_texts[key].grid(row=button_row,column=button_column)
       self.reconfigure_specific_buttons(key, text_button)  


   def reconfigure_specific_buttons(self, key, text_button):
       self.reconfigure_clear_button(key, text_button)
       self.reconfigure_equals_button(key, text_button)     
       self.reconfigure_divide_button(key, text_button)


   def reconfigure_clear_button(self, key, text_button):
       if text_button == 'A':
           self.calculator_buttons_texts[key].config(bg='#E6D72A', text='AC')
           self.calculator_buttons_texts[key].grid(columnspan=4, sticky=tk.W+tk.E)

   def reconfigure_equals_button(self, key, text_button):
       if text_button == '=':
           self.calculator_buttons_texts[key].config(bg='#E6D72A')
           self.calculator_buttons_texts[key].grid(columnspan=2, sticky=tk.W+tk.E)

   def reconfigure_divide_button(self, key, text_button):
       if text_button == '/':
           self.calculator_buttons_texts[key].grid(column=3)  

   def create_calculator_input_field(self):
       self.user_input = tk.Entry(self.master, justify=tk.RIGHT, width=24, insertwidth=4, bd=29)
       self.user_input.grid(row=0, column=0, columnspan=4)
       self.user_input.configure(font = ("Verdana", 20, "bold"), bg="#5BC8AC")
       self.user_input.insert(0, "0")


if __name__ == "__main__":
    root = tk.Tk()
    Calculator(root)
    root.mainloop()
\$\endgroup\$
  • \$\begingroup\$ Thank you for taking your time to type such a detailed answer!!! I was waiting for an answer like this because i am still a beginner in python and i do not even know about the basics as of yet! I basically self learn python using only Youtube and/or Google... and i have no other way for people to correct my code. This advice will be of great help to me! Much appreciated! :) @Billal \$\endgroup\$ – Akil Riaz Apr 28 '17 at 14:50
  • \$\begingroup\$ Keep in my mind, that I coded only the interface of your calculator. It is better to create a different class for the mathematical operations. You can continue your work based on what I provided. You can ask me any day you want to clarify you anything you do not understand in my code above. I basically self learn python using only Youtube and/or Google I respect you for your brave attitude. @AkilRiaz \$\endgroup\$ – Billal Begueradj Apr 28 '17 at 14:54
  • \$\begingroup\$ Understood! :) @Billal \$\endgroup\$ – Akil Riaz Apr 28 '17 at 15:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.