October 29, 2020

GUI Development with Tkinter, Python

Prerequisites

  • Python 3.8
  • Basics of Python

So, you’re a python developer and you don’t want to learn a whole new programming language just for GUI (Graphical User Interface) development. It is possible to build a GUI application in Python by using the Tkinter library. Let’s create a small application which adds two numbers. This is what we are going to build:

We will be using PyCharm Professional as it can be done using the PyCharm Community Edition too. It’s a very good tool but if you’re willing to use a code editor like ATOM, VSCode, or any of your favourites. You’re welcome to. We will start by creating a python file and adding the following code:

def main():
    print("Our main function")


if __name__ == '__main__':
    main()

As we are going to create this single file and run it directory we use if __name__ == '__main__': main(). Here main() function points to the main function above in the code which if you run now, will execute by printing Our main function in the console. After confirming python is executing our code, we are going to create a simple window using Tkinter. Enter the following code:

from tkinter import *
from tkinter import ttk


def main():
    master = Tk()
    ttk.Label(master, text="Hi, I'm Tkinter.").pack()
    master.mainloop()


if __name__ == '__main__':
    main()

We have imported everything from the tkinter module and from the same module we have imported ttk which are themed widgets in the world of Tkinter. Within the main() function, we have created an instance of Tkinter Tk() which is the top level window and then we have created a ttk.Label which displays text and provided some arguments to it. The first argument is the master for the ttk.Label and second is the text value which we want the label to display. The master argument is asking for the top-level window on which the ttk.Label widget is going to be placed. After the arguments, we have used a function pack() which is a geometry manager, it simply places the widget into the top-level level window and at the last, we have used master.mainloop() which blocks or halts the execution for the window to be displayed. Run the program to see a small window showing the text. You can resize the window for your comfort.

Rather than creating a mess in the main() function, let’s create a class within the file and pass the master to it. Here’s how we can do it:

from tkinter import *
from tkinter import ttk


class AddNumApp:

    def __init__(self, master):

        ttk.Label(master, text="Hi, I'm Tkinter.").pack()


def main():
    master = Tk()
    AddNumApp(master)
    master.mainloop()


if __name__ == '__main__':
    main()

We have moved the ttk.Label into a class called AddNumApp and within the constructor, we have added the master parameter too. In the main() function, we have directly called the class and passed master to it making our code more understandable and clean. Let’s change the window title by adding the following line above the ttk.Label within the body of the constructor.

# window title
master.title("Add Numbers")

This line of code sets the title for the top-level window. If you run and resize the window, you can see the title changed. Now let’s change the whole theme using ttk.Style. Add the following code above ttk.Label

# style object
self.style = ttk.Style()
# use default theme
self.style.theme_use('default')

First, we get the Style (ttk.Style()) object and store it as a class variable using the self keyword. Then using the style variable we change the theme by using the theme_use method and setting 'default' as a string to use the default theme. You won’t see a very big difference yet but when we will add more widgets, the changes will appear. Let’s change the ttk.Label and add some arguments to change how it looks and using the grid geometry place it in a proper way.

# create label widget which shows the result
self.result_view = ttk.Label(master, text="0", background="#FFFFFF")
self.result_view.grid(row=0, column=0, padx=10, pady=22, columnspan=2)

We change the ttk.Label to show the result of the addition. Let’s go through every argument placed in the ttk.Label

  • master – The top-level window in which the widget is going to be placed
  • text – String value the label will display
  • background – Sets the background of the widget. "#FFFFFF" is total white

This time we did not use the .pack() geometry method to place the widget. We used .grid() which makes the window a grid structure which consists of rows and columns. We have passed some arguments to it. Let’s go through each of it.

  • row – sets the row
  • column – sets the column
  • padx – sets the padding according to the x-axis
  • pady – sets the padding according to the y-axis
  • columnspan – merges the number of columns provided. As we have passed 2 so the widget will be placed across column 0 and column 1.

Using the ttk.Entry widget we can take input from the user. We need 2 values which are going to be added. So, now we add two ttk.Entry widgets and we will place them horizontally next to each other using the grid geometry management system.

# create entry widget where user inputs the first value
self.first_value = ttk.Entry(master, width=10)
self.first_value.grid(row=1, column=0, padx=10, pady=10)

# create entry widget where user inputs the second value
self.second_value = ttk.Entry(master, width=10)
self.second_value.grid(row=1, column=1, padx=10, pady=10)

Here we have added two ttk.Entry widgets. self.first_value where the user can input the first value and self.second_value where the user can input the second value. Both are placed using the grid geometry management system. We have placed the first_value widget in the first column which is 0 and the second_value widget in the second column which is 1. Now, both the ttk.Entry widgets are horizontally placed next to each other. Within the ttk.Entry widget we have called a new argument called width and set it to 10 for both which makes the width of the widget according to the number provided.

Now, let’s add two buttons, one which will add both the values in the ttk.Entry widgets and another button which will clear the input from the ttk.Entry widgets and set the ttk.Label value to 0.

# create button widget which adds first and second value
ttk.Button(master, width=8, text="Add").grid(row=2, column=0, padx=10, pady=5)

# create button widget which clears input
ttk.Button(master, width=8, text="Clear").grid(row=2, column=1, padx=10, pady=12)

If we run the program, we can see two buttons, one is Add and another one is Clear. As we don’t need to save or alter the buttons we will not store them in a variable, we will directly call the ttk.Button class and add the required parameters. Let’s create a function within the AddNumApp class for the add button.

def add(self):
    # 1. get value from self.first_value
    # 2. get value from self.second_value
    # 3. validate self.first_value is not empty
    # 4. validate self.second_value is not empty
    # 5. add both values
    # 6. set the calculated value to self.result_view
    pass

Within the AddNumApp class we have created a function which contains the steps of how we will get, validate and set the value. Let’s go through each step.

# 1. get value from self.first_value

first_value = self.first_value.get()

# 2. get value from self.second_value

second_value = self.second_value.get()

# 3. validate self.first_value is not empty

if first_value == "":
    messagebox.showerror("Value Error", "Enter first value")
    return

The messagebox has multiple functions like showerror, showinfo, showwarning. We are using the showerror message as we don’t need the ttk.Entry widget to be empty. The showerror function takes in two parameters. One is the title, and the second is the message. For accessing the messagebox add the following import at the top of the file.

from tkinter import messagebox

# 4. validate self.second_value is not empty

if second_value == "":
    messagebox.showerror("Value Error", "Enter second value")
    return

# 5. add both values

We are going to create a static method within the AddNumApp class which will take two values and return a single value by adding both the values given to it. Here’s how

@staticmethod
def add_num(first, second):
    return first + second

Now using the add_num method we will add both the values from both the ttk.Entry widgets. Let’s use the add_num method to add the values and save them in a variable called result

result = self.add_num(float(first_value), float(second_value))

The return value from both the ttk.Entry widgets are stored as a string so we need to convert it to float. Converting it to float makes the addition possible or otherwise, both the strings can add up into a single string which isn’t the result we desire. We have added both the values and stored the return value in the variable result. Let’s go through the last step.

# 6. set the calculated value to self.result_view

self.result_view.configure(text='{} + {} = {}'.format(str(first_value), str(second_value), str(result)))

Using the configure method we are changing the text of the ttk.Label (self.result_view).

Here comes a question. What if the user enters an alphabet which cannot be added? How can we catch that? We can learn from doing such an experiment. First, we will append the add method to the Add ttk.Button by setting the command argument.

ttk.Button(master, width=8, text="Add", command=self.add).grid(row=2, column=0, padx=10, pady=5)

Using the command argument, we set the function to the Add ttk.Button. To check, run the program and enter numbers in both the ttk.Entry widgets. We can see how the addition of both the numbers happens and the result is displayed in the ttk.Label. Now, try entering an alphabet and a number and add them. What do you get? A ValueError? Yes, because we are converting the values from the ttk.Entry widget to float, we will receive a ValueError. How can we tackle this problem?

The most easy solution is to catch that exception and rather than encountering an error, we can display a messagebox which notifies the user of the mistake. So, cut all the code within the add function and place it in the try block and using except we can catch the ValueError and display a messagebox. Here’s how

def add(self):
    try:
        # get value from self.first_value
        first_value = self.first_value.get()
        # get value from self.second_value
        second_value = self.second_value.get()

        # validate self.first_value is not empty
        if first_value == "":
            messagebox.showerror("Value Error", "Enter first value")
            return

        # validate self.second_value is not empty
        if second_value == "":
            messagebox.showerror("Value Error", "Enter second value")
            return

        # add both values
        result = self.add_num(float(first_value), float(second_value))

        # set the calculated value to self.result_view
        self.result_view.configure(text='{} + {} = {}'.format(str(first_value), str(second_value), str(result)))

    except ValueError:
        # displaying error message box
        messagebox.showerror("Invalid input", "Invalid input detected. Numbers are only supported")

This is how we catch the ValueError. Now, we know that the user will be able to input numbers only within the ttk.Entry widgets. The last part is to add the clear button logic. What will the clear button do? It should delete the input within the ttk.Entry widgets and set the text value of ttk.Label which displays the result to 0. Let’s see how

# clears input in entry widget and sets 0 in label widget
def clear(self):
    self.first_value.delete(0, 'end')
    self.second_value.delete(0, 'end')
    self.result_view.configure(text='0')

The delete method is available for ttk.Entry only. The delete method takes two arguments, first and last which means from where to start deleting and till where to delete. We start from 0 which is the index value of the first word in the ttk.Entry widget and use the 'end' string which means to delete till the end of whatever is written in the ttk.Entry widget. For the result_view we use the same configure method and set the text argument to '0'. Let’s assign the clear function to the clear ttk.Button.

ttk.Button(master, width=8, text="Clear", command=self.clear).grid(row=2, column=1, padx=10, pady=12)

A useful technique is to clear the input whenever an invalid input is submitted so we can call the clear function in the except part of the add function after the messagebox is displayed.

except ValueError:
    # displaying error message box
    messagebox.showerror("Invalid input", "Invalid input detected. Numbers are only supported")
    # clearing input
    self.clear()

Now run the program and enter values in the ttk.Entry widgets and using the Add button, add both the values. Test the clear button. Here is the final result of what the python file will look like:

from tkinter import *
from tkinter import ttk
from tkinter import messagebox


class AddNumApp:

    def __init__(self, master):

        # window title
        master.title("Add Numbers")
        # disable resize of window in all directions
        master.resizable(False, False)

        # style object
        self.style = ttk.Style()
        # use default theme
        self.style.theme_use('default')

        # create label widget which shows the result
        self.result_view = ttk.Label(master, text="0", background="#FFFFFF")
        self.result_view.grid(row=0, column=0, padx=10, pady=22, columnspan=2)

        # create entry widget where user inputs the first value
        self.first_value = ttk.Entry(master, width=10)
        self.first_value.grid(row=1, column=0, padx=10, pady=10)

        # create entry widget where user inputs the second value
        self.second_value = ttk.Entry(master, width=10)
        self.second_value.grid(row=1, column=1, padx=10, pady=10)

        # create button widget which adds first and second value
        ttk.Button(master, width=8, text="Add", command=self.add).grid(row=2, column=0, padx=10, pady=5)

        # create button widget which clears input
        ttk.Button(master, width=8, text="Clear", command=self.clear).grid(row=2, column=1, padx=10, pady=12)

    def add(self):
        try:
            # get value from self.first_value
            first_value = self.first_value.get()
            # get value from self.second_value
            second_value = self.second_value.get()

            # validate self.first_value is not empty
            if first_value == "":
                messagebox.showerror("Value Error", "Enter first value")
                return

            # validate self.second_value is not empty
            if second_value == "":
                messagebox.showerror("Value Error", "Enter second value")
                return

            # add both values
            result = self.add_num(float(first_value), float(second_value))

            # set the calculated value to self.result_view
            self.result_view.configure(text='{} + {} = {}'.format(str(first_value), str(second_value), str(result)))

        except ValueError:
            # displaying error message box
            messagebox.showerror("Invalid input", "Invalid input detected. Numbers are only supported")
            # clearing input
            self.clear()

    # returns a number by adding first and second
    @staticmethod
    def add_num(first, second):
        return first + second

    # clears input in entry widget and sets 0 in label widget
    def clear(self):
        self.first_value.delete(0, 'end')
        self.second_value.delete(0, 'end')
        self.result_view.configure(text='0')


def main():
    master = Tk()
    AddNumApp(master)
    master.mainloop()


if __name__ == '__main__':
    main()

Here you can access the whole updated code.

We have some challenges for you if you’re willing to improve your python tkinter skills. Try adding the following:

  1. Subtract functionality
  2. Division functionality
  3. Multiplication functionality

Let us know in the comments if you’ve completed the tutorial or you’re stuck somewhere. Like and share to help us grow.