Elevating Download Progress Visualization with Python: A Tkinter Twist

Umesh Sujakhu
codingmountain
Published in
5 min readJan 22, 2024
Photo by Mike van den Bos on Unsplash

Introduction

If you’ve been following my previous post on Minimizing Installer Size: A Python Script for Efficient S3 Download and Execution, you might have gained insights into tracking download progress in Python using the command line terminal. In this article, we’re taking it a step further by integrating Tkinter to provide a more interactive and visually pleasing graphical user interface (GUI) for our download progress.

If you haven’t read my previous post yet, I recommend checking it out here. It lays the groundwork for what we’re about to enhance.

Recap of Previous Code

In the previous post, we delved into the fundamentals of download progress in Python. Let’s briefly recap the key aspects:

from tqdm import tqdm

def download_file_with_progress(self):
# Get the size of the S3 object for tqdm progress bar
response = self.s3.head_object(Bucket=self.bucket_name, Key=self.object_key)
file_size = response["ContentLength"]

# Use tqdm to display a progress bar
with tqdm(
total=file_size,
desc="Downloading...Please wait...",
unit="B",
unit_scale=True,
) as pbar:
self.s3.download_file(
self.bucket_name, self.object_key, self.local_path, Callback=pbar.update
)

def execute_downloaded_file(self):
# Execute the downloaded executable
os.system(self.local_path)

This code served us well for basic download tasks using tqdm and showing the download progress in the command prompt terminal black screen, but now it’s time to take it a step further.

Introducing Tkinter for a Visual Download Experience

In this iteration, we’re going to make our download progress more visually appealing by incorporating the Tkinter library. This will provide us with a sleek progress bar, enhancing the overall user experience.

Changes Made

  1. Added the Tkinter library to the project.
  2. Utilized Tkinter to visualize the download progress.
pip install tk

Here’s a glimpse of the changes made:

import boto3
import os
import threading
import tkinter as tk
from tkinter import ttk
from tqdm import tqdm
import subprocess


class S3Service:
def __init__(self):
self.aws_access_key = "YOUR_AWS_ACCESS_KEY"
self.aws_secret_key = "YOUR_AWS_SECRET_KEY"

self.s3 = boto3.client(
"s3",
aws_access_key_id=self.aws_access_key,
aws_secret_access_key=self.aws_secret_key,
)

def set_s3_parameters(self, bucket_name, object_key, local_path):
self.bucket_name = bucket_name
self.object_key = object_key
self.local_path = os.path.join(local_path, object_key)

def download_file_with_progress(self):
try:
# Create a new thread for the download
download_thread = threading.Thread(target=self.download_file, daemon=True)
download_thread.start()

# Start the Tkinter main loop in the main thread
self.root = tk.Tk()
self.root.title("Downloading")

# Calculate screen width and height
screen_width = self.root.winfo_screenwidth()
screen_height = self.root.winfo_screenheight()

# Calculate the x and y coordinates to center the window
x_coordinate = int((screen_width - 400) / 2) # 400 is the window width
y_coordinate = int((screen_height - 100) / 2) # 100 is the window height

# Set the geometry of the window to center it on the screen
self.root.geometry(f"400x100+{x_coordinate}+{y_coordinate}")

# Use indeterminate mode for a spinner-like effect
progress_bar = ttk.Progressbar(self.root, mode="indeterminate")
progress_bar.pack(pady=10)

label = tk.Label(self.root, text="Downloading... Please wait.")
label.pack(pady=10)

# Start the indeterminate progress bar
progress_bar.start()

# Run the Tkinter main loop
self.root.mainloop()

except KeyboardInterrupt:
print("Download interrupted.")
self.destroy_window()

def download_file(self):
try:
response = self.s3.head_object(Bucket=self.bucket_name, Key=self.object_key)
file_size = response["ContentLength"]

with tqdm(total=file_size, unit="B", unit_scale=True, disable=True) as pbar:
self.s3.download_file(
self.bucket_name, self.object_key, self.local_path
)

# Close the Tkinter GUI window after 5 seconds of download completes
self.root.after(5000, self.destroy_window)

except Exception as e:
print(f"Error during download: {e}")
self.destroy_window()

def destroy_window(self):
self.root.destroy()

def execute_downloaded_file(self):
# Execute the downloaded executable
subprocess.Popen([self.local_path], shell=True)

Importing Modules:

  • boto3: The AWS SDK for Python, used for interacting with AWS services.
  • os: Provides a way of interacting with the operating system.
  • threading: Allows for the creation and synchronization of threads.
  • tkinter: The standard GUI toolkit for Python.
  • ttk: Tkinter-themed widgets, including a progress bar.
  • tqdm: A library for creating progress bars.
  • subprocess: Enables the creation of additional processes.

All the codes except for the function downlaod_file_with_progress are the same as in my previous post linked above. So I am going to explain changes made in this function only.

The functiondownload_file_with_progress creates a new thread for the download using the functionthreading.Thread and starts the Tkinter main loop to display a GUI window during the download. It initializes a Tkinter window, progress bar, and label for a visual representation of the download progress.

download_file function uses tqdm to download files from S3 which is already explained in my previous article. The only change is that it initiates a Tkinter’s after event to close the GUI window after 5 seconds of download completion done in the functiondestroy_window.

Here we have used subprocess.Popen instead of os.system , both do the same job but subprocessprovides more flexibility, and you can capture output or interact with the process using the returned Popen object.

Now let us see how the output looks when we run the script python main.py:

To generate a standalone executable file :

pyinstaller --onefile --noconsole --icon=icon.ico main.py
  • — noconsole : This option is used to suppress the console window that typically appears when running a command-line script. If your Python script doesn't rely on user input or console output, and you want to run it silently without displaying a terminal window, you can use this option.
  • --icon=icon.ico (Optional): This option allows you to specify an icon file (with the .ico extension) for the generated executable. The specified icon will be used as the icon for the executable file.

Why Use Tkinter?

  • Interactive Progress: Tkinter allows us to create an interactive progress bar, providing users with a more engaging download experience.
  • Graphical Appeal: The graphical interface adds a visual element to the download process, making it more intuitive and user-friendly.

Conclusion

By incorporating Tkinter into our download functionality, we have transformed a command-line process into an interactive and visually appealing experience. Users can now enjoy a graphical representation of the download progress, enhancing the overall usability of our application.

The use of threading ensures a responsive interface, while Tkinter and tqdm enhance the user experience during the installation process.

Feel free to customize the Tkinter code to match your application’s aesthetics and requirements. As always, your feedback is valued, and if you have any questions, feel free to reach out!

Happy coding!

--

--