In a previous tutorial we saw the basic concepts behind the usage of Tkinter, a library used to create graphical user interfaces with Python. In this article we see how to create a complete although simple application. In the process, we learn how to use threads to handle long running tasks without blocking the interface, how to organize a Tkinter application using an object oriented approach, and how to use Tkinter protocols.
In this tutorial you will learn:
- How to organize a Tkinter application using an object-oriented approach
- How to use threads to avoid blocking the application interface
- How to use make threads communicate by using events
- How to use Tkinter protocols

Software requirements and conventions used
Category | Requirements, Conventions or Software Version Used |
---|---|
System | Distribution-independent |
Software | Python3, tkinter |
Other | Knowledge of Python and Object Oriented Programming concepts |
Conventions | # – requires given linux-commands to be executed with root privileges either directly as a root user or by use of sudo command$ – requires given linux-commands to be executed as a regular non-privileged user |
Introduction
In this tutorial we will code a simple application “composed” of two widgets: a button and a progress bar. What our application will do, is just to download the tarball containing the latest WordPress release once the user clicks on the “download” button; the progress bar widget will be used to keep track of the download progress. The application will be coded by using an object oriented approach; in the course of the article I will assume the reader to be familiar with OOP basic concepts.
Organizing the application
The very first thing we need to do in order to build our application is to import the needed modules. For starters we need to import:
- The base Tk class
- The Button class we need to instantiate to create the button widget
- The Progressbar class we need to create the progress bar widget
The first two can be imported from the tkinter
module, while the latter, Progressbar
, is included in the tkinter.ttk
module. Let’s open our favorite text editor and start writing the code:
#!/usr/bin/env python3
from tkinter import Tk, Button
from tkinter.ttk import Progressbar
We want to build our application as a class, in order to keep data and functions well organized, and avoid cluttering the global namespace. The class representing our application (let’s call it
WordPressDownloader
), will extend the Tk
base class, which, as we saw in the previous tutorial, is used to create the “root” window:
class WordPressDownloader(Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.title('Wordpress Downloader')
self.geometry("300x50")
self.resizable(False, False)
Let’s see what the code we just wrote does. We defined our class as a subclass of Tk
. Inside its constructor we initialized the parent, than we set our application title and geometry by calling the title
and geometry
inherited methods, respectively. We passed the title as argument to the title
method, and the string indicating the geometry, with the <with>x<height>
syntax, as argument to the geometry
method.
We than set the root window of our application as non-resizable. We achieved that by calling the resizable
method. This method accepts two boolean values as arguments: they establish whether the width and height of the window should be resizable. In this case we used False
for both.
At this point, we can create the widgets which should “compose” our application: the progress bar and the “download” button. We add the following code to our class constructor (previous code omitted):
# The progressbar widget
self.progressbar = Progressbar(self)
self.progressbar.pack(fill='x', padx=10)
# The button widget
self.button = Button(self, text='Download')
self.button.pack(padx=10, pady=3, anchor='e')
We used the Progressbar
class to create the progress bar widget, and than called the pack
method on the resulting object to create a minimum of setup. We used the fill
argument to make the widget occupy all the available width of the parent window (x axis), and the padx
argument to create a margin of 10 pixels from its left and right borders.
The button was created by instantiating the Button
class. In the class constructor we used the text
parameter to set the button text. We than setup the button layout with pack
: with the anchor
parameter we declared that the button should be kept at the right of the main widget. The anchor direction is specified by using compass points; in this case, the e
stands for “east” (this can be also specified by using constants included in the tkinter
module. In this case, for example, we could have used tkinter.E
). We also set the same horizontal margin we used for the progress bar.
When creating the widgets, we passed self
as the first argument of their classes constructors in order to set the window represented by our class as their parent.
We didn’t define a callback for our button yet. For now, let’s just see how our application looks. In order to do that we have to append the main sentinel to our code, create an instance of the WordPressDownloader
class, and call the mainloop
method on it:
if __name__ == '__main__':
app = WordPressDownloader()
app.mainloop()
At this point we can make our script file executable and launch it. Supposing the file is named app.py
, in our current working directory, we would run:
$ chmod +x app.py ./app.py
We should obtain the following result:

All seems good. Now let’s make our button do something! As we saw in the basic tkinter tutorial, to assign an action to a button, we must pass the function we want to use as callback as the value of the command
parameter of the Button
class constructor. In our application class, we define the handle_download
method, write the code which will perform the download, and than assign the method as the button callback.
To perform the download, we will make use of the urlopen
function which is included in the urllib.request
module. Let’s import it:
from urllib.request import urlopen
Here is how we implement the handle_download
method:
def handle_download(self):
with urlopen("https://wordpress.org/latest.tar.gz") as request:
with open('latest.tar.gz', 'wb') as tarball:
tarball_size = int(request.getheader('Content-Length'))
chunk_size = 1024
read_chunks = 0
while True:
chunk = request.read(chunk_size)
if not chunk:
break
read_chunks += 1
read_percentage = 100 * chunk_size * read_chunks / tarball_size
self.progressbar.config(value=read_percentage)
tarball.write(chunk)
The code inside the handle_download
method is quite simple. We issue a get request to download the latest WordPress release tarball archive and we open/create the file we will use to store the tarball locally in wb
mode (binary-write).
To update our progress bar we need to obtain the amount of downloaded data as a percentage: in order to do that, first we obtain the total size of the file by reading the value of the Content-Length
header and casting it to int
, than we establish that the file data should be read in chunks of of 1024 bytes
, and keep the count of chunks we read using the read_chunks
variable.
Inside the infinite
while
loop, we use the read
method of the request
object to read the amount of data we specified with chunk_size
. If the read
methods returns an empty value, it means that there is no more data to read, therefore we break the loop; otherwise, we update the amount of chunks we read, calculate the download percentage and reference it via the read_percentage
variable. We use the computed value to update the progress bar by calling its config
method. Finally, we write the data to the local file.
We can now assign the callback to the button:
self.button = Button(self, text='Download', command=self.handle_download)
It looks like everything should work, however, once we execute the code above and click on the button to start the download, we realize there is a problem: the GUI becomes unresponsive, and the progress bar is updated all at once when the download is completed. Why this happens?
Our application behaves this way since the handle_download
method runs inside the main thread and blocks the main loop: while the download is being performed, the application cannot react to user actions. The solution to this problem is to execute the code in a separate thread. Let’s see how to do it.
Using a separate thread to perform long-running operations
What is a thread? A thread is basically a computational task: by using multiple threads we can make specific parts of a program be executed independently. Python makes very easy to work with threads via the threading
module. The very first thing we need to do, is to import the Thread
class from it:
from threading import Thread
To make a piece of code be executed in a separate thread we can either:
- Create a class which extends the
Thread
class and implements therun
method - Specify the code we want to execute via the
target
parameter of theThread
object constructor
Here, to make things better organized, we will use the first approach. Here is how we change our code. As a first thing, we create a class which extends Thread
. First, in its constructor, we define a property which we use to keep track of the download percentage, than, we implement the run
method and we move the code which performs the tarball download in it:
class DownloadThread(Thread):
def __init__(self):
super().__init__()
self.read_percentage = 0
def run(self):
with urlopen("https://wordpress.org/latest.tar.gz") as request:
with open('latest.tar.gz', 'wb') as tarball:
tarball_size = int(request.getheader('Content-Length'))
chunk_size = 1024
read_chunks = 0
while True:
chunk = request.read(chunk_size)
if not chunk:
break
read_chunks += 1
self.read_percentage = 100 * chunk_size * read_chunks / tarball_size
tarball.write(chunk)
Now we should change the constructor of our WordPressDownloader
class so that it accepts an instance of DownloadThread
as argument. We could also create an instance of DownloadThread
inside the constructor, but by passing it as argument, we explicitly declare that WordPressDownloader
depends on it:
class WordPressDownloader(Tk):
def __init__(self, download_thread, *args, **kwargs):
super().__init__(*args, **kwargs)
self.download_thread = download_thread
[...]
What we want to do now, is to create a new method which will be used to keep track of the percentage progress and will update the value of the progress bar widget. We can call it update_progress_bar
:
def update_progress_bar(self):
if self.download_thread.is_alive():
self.progressbar.config(value=self.download_thread.read_percentage)
self.after(100, self.update_progress_bar)
In the update_progress_bar
method we check if the thread is running by using the is_alive
method. If the thread is running we update the progress bar with the value of the read_percentage
property of the thread object. After this, to keep monitoring the download, we use the after
method of the WordPressDownloader
class. What this method does is to perform a callback after a specified amount of milliseconds. In this case we used it to re-call the update_progress_bar
method after 100
milliseconds. This will be repeated until the thread is alive.
Finally, we can modify the content of the handle_download
method which is invoked when the user clicks on the “download” button. Since the actual download is performed in the run
method of the DownloadThread
class, here we just need to start the thread, and invoke the update_progress_bar
method we defined in the previous step:
def handle_download(self):
self.download_thread.start()
self.update_progress_bar()
At this point we must modify how the app
object is created:
if __name__ == '__main__':
download_thread = DownloadThread()
app = WordPressDownloader(download_thread)
app.mainloop()
If we now re-launch our script and start the download we can see that the interface is not blocked anymore during the download:

There is still a problem however. To “visualize” it, launch the script, and close the graphical interface window once the download has started but is not yet finished; do you see that there is something hanging the terminal? This happens because while the main thread has been closed, the one used to perform the download is still running (data is still being downloaded). How can we solve this problem? The solution is to use “events”. Let’s see how.
Using events
By using an Event
object we can establish a communication between threads; in our case between the main thread and the one we are using to perform the download. An “event” object is initialized via the Event
class we can import from the threading
module:
from threading import Thread, Event
How does an event object work? An Event object has a flag which can be set to True
via the set
method, and can be reset to False
via the clear
method; its status can be checked via the is_set
method. The long task executed in the run
function of the thread we built to perform the download, should check the flag status before performing each iteration of the while loop. Here is how we change our code. First we create an event and bind it to a property inside the DownloadThread
constructor:
class DownloadThread(Thread):
def __init__(self):
super().__init__()
self.read_percentage = 0
self.event = Event()
Now, we should create a new method in the DownloadThread
class, which we can use to set the flag of the event to False
. We can call this method stop
, for example:
def stop(self):
self.event.set()
Finally, we need to add an additional condition in the while loop in the run
method. The loop should be broken if there are no more chunks to read, or if the the event flag is set:
def run(self):
[...]
while True:
chunk = request.read(chunk_size)
if not chunk or self.event.is_set():
break
[...]
What we need to do now, is to call the stop
method of the thread when the application window is closed, so we need to catch that event.
Tkinter protocols
The Tkinter library provides a way to handle certain events that happens to the application by using protocols. In this case we want to perform an action when the user clicks on the button to close the graphical interface. To achieve our goal we must “catch” the WM_DELETE_WINDOW
event and run a callback when it is fired. Inside the WordPressDownloader
class constructor, we add the following code:
self.protocol('WM_DELETE_WINDOW', self.on_window_delete)
The first argument passed to the protocol
method is the event we want catch, the second is the name of the callback which should be invoked. In this case the callback is: on_window_delete
. We create the method with the following content:
def on_window_delete(self):
if self.download_thread.is_alive():
self.download_thread.stop()
self.download_thread.join()
self.destroy()
As you can recall, the download_thread
property of our WordPressDownloader
class references the thread we used to perform the download. Inside the on_window_delete
method we check if the thread has been started. If is the case, we call the stop
method we saw before, and than the join
method which is inherited from the Thread
class. What the latter does, is blocking the calling thread (in this case the main one) until the thread on which the method is invoked terminates. The method accepts an optional argument which must be a floating point number representing the maximum number of seconds the calling thread will wait for the other one (in this case we don’t use it). Finally, we invoke the destroy
method on our WordPressDownloader
class, which kills the window and all the descendant widgets.
Here is the complete code we wrote in this tutorial:
#!/usr/bin/env python3
from threading import Thread, Event
from urllib.request import urlopen
from tkinter import Tk, Button
from tkinter.ttk import Progressbar
class DownloadThread(Thread):
def __init__(self):
super().__init__()
self.read_percentage = 0
self.event = Event()
def stop(self):
self.event.set()
def run(self):
with urlopen("https://wordpress.org/latest.tar.gz") as request:
with open('latest.tar.gz', 'wb') as tarball:
tarball_size = int(request.getheader('Content-Length'))
chunk_size = 1024
readed_chunks = 0
while True:
chunk = request.read(chunk_size)
if not chunk or self.event.is_set():
break
readed_chunks += 1
self.read_percentage = 100 * chunk_size * readed_chunks / tarball_size
tarball.write(chunk)
class WordPressDownloader(Tk):
def __init__(self, download_thread, *args, **kwargs):
super().__init__(*args, **kwargs)
self.download_thread = download_thread
self.title('Wordpress Downloader')
self.geometry("300x50")
self.resizable(False, False)
# The progressbar widget
self.progressbar = Progressbar(self)
self.progressbar.pack(fill='x', padx=10)
# The button widget
self.button = Button(self, text='Download', command=self.handle_download)
self.button.pack(padx=10, pady=3, anchor='e')
self.download_thread = download_thread
self.protocol('WM_DELETE_WINDOW', self.on_window_delete)
def update_progress_bar(self):
if self.download_thread.is_alive():
self.progressbar.config(value=self.download_thread.read_percentage)
self.after(100, self.update_progress_bar)
def handle_download(self):
self.download_thread.start()
self.update_progress_bar()
def on_window_delete(self):
if self.download_thread.is_alive():
self.download_thread.stop()
self.download_thread.join()
self.destroy()
if __name__ == '__main__':
download_thread = DownloadThread()
app = WordPressDownloader(download_thread)
app.mainloop()
Let’s open a terminal emulator and launch our Python script containing the above code. If we now close the main window when the download is still being performed, the shell prompt comes back, accepting new commands.
Summary
In this tutorial we built a complete graphical application using Python and the Tkinter library using an object oriented approach. In the process we saw how to use threads to perform long running operations without blocking the interface, how to use events to let a thread communicate with another, and finally, how to use Tkinter protocols to perform actions when certain interface events are fired.