I am posting this because I myself have struggled with finding a clear answer on this problem . . .
In search of trying to create a progress bar for my program, I find that it is difficult to do using tkinter. To accomplish creating a progress bar without running into the dreaded "mainloop", I opted to make a class out of the progress bar using threads. Through lots of trial an error, I found that there is not much that can be customized due to the use of multithreading (tkinter likes being in the main thread). Here are two options I have tried, followed by a third that best fits my needs:
Option 1: Using a callback function
Given the following code:
import tkinter as tk
import tkinter.ttk as ttk
import threading
class ProgressbarApp(threading.Thread):
def __init__(self, max_value: int):
self.max_value = max_value
self.root = None
self.pb = None
threading.Thread.__init__(self)
self.lock = threading.Lock() # (1)
self.lock.acquire() # (2)
self.start()
# (1) Makes sure progressbar is fully loaded before executing anything
with self.lock:
return
def close(self):
self.root.quit()
def run(self):
self.root = tk.Tk()
self.root.protocol("WM_DELETE_WINDOW", self.__callback)
self.pb = ttk.Progressbar(self.root, orient='horizontal', length=400, mode='determinate')
self.pb['value'] = 0
self.pb['maximum'] = self.max_value
self.pb.pack()
self.lock.release() # (2) Will release lock when finished
self.root.mainloop()
def update(self, value: int):
self.pb['value'] = value
@staticmethod
def __callback():
return
if __name__ == '__main__':
interval = 100000
my_pb = ProgressbarApp(interval)
for i in range(interval):
my_pb.update(i)
my_pb.close()
# Other stuff goes on . . .
Where
self.root.protocol("WM_DELETE_WINDOW", self.__callback)
Prevents the window from being closed. However, if the Exit, or [ X ], button were to be held down, the progress bar would freeze until the user releases the button. (The __callback function is constantly being called, preventing other tasks from being completed).
Option 2: Using root.overriderdirect(True)
Given the following code:
import tkinter as tk
import tkinter.ttk as ttk
import threading
class ProgressbarApp(threading.Thread):
def __init__(self, max_value: int):
self.max_value = max_value
self.root = None
self.pb = None
threading.Thread.__init__(self)
self.lock = threading.Lock() # (1)
self.lock.acquire() # (2)
self.start()
# (1) Makes sure progressbar is fully loaded before executing anything
with self.lock:
return
def close(self):
self.root.quit()
def run(self):
self.root = tk.Tk()
self.root.overrideredirect(True)
self.pb = ttk.Progressbar(self.root, orient='horizontal', length=400, mode='determinate')
self.pb['value'] = 0
self.pb['maximum'] = self.max_value
self.pb.pack()
self.lock.release() # (2) Will release lock when finished
self.root.mainloop()
def update(self, value: int):
self.pb['value'] = value
if __name__ == '__main__':
interval = 100000
my_pb = ProgressbarApp(interval)
for i in range(interval):
my_pb.update(i)
my_pb.close()
# Other stuff goes on . . .
Where
self.root.overrideredirect(True)
Clears all of tkinters window options. However, the progress bar is not only in an odd location, but it also obscures the users window. The progress bar should user friendly.
Option 3: Using root.attributes('-disabled', True)
Given the following code:
import tkinter as tk
import tkinter.ttk as ttk
import threading
class ProgressbarApp(threading.Thread):
def __init__(self, max_value: int):
self.max_value = max_value
self.root = None
self.pb = None
threading.Thread.__init__(self)
self.lock = threading.Lock() # (1)
self.lock.acquire() # (2)
self.start()
# (1) Makes sure progressbar is fully loaded before executing anything
with self.lock:
return
def close(self):
self.root.quit()
def run(self):
self.root = tk.Tk()
self.root.attributes('-disabled', True)
self.pb = ttk.Progressbar(self.root, orient='horizontal', length=400, mode='determinate')
self.pb['value'] = 0
self.pb['maximum'] = self.max_value
self.pb.pack()
self.lock.release() # (2) Will release lock when finished
self.root.mainloop()
def update(self, value: int):
self.pb['value'] = value
if __name__ == '__main__':
interval = 100000
my_pb = ProgressbarApp(interval)
for i in range(interval):
my_pb.update(i)
my_pb.close()
# Other stuff goes on . . .
Where
self.root.attributes('-disabled', True)
Prevents any user interaction with the window. This has best suited my needs for this program as it prevents the window from closing and still has a nice appearance to it. (My only minor issue with it is that the user can no longer minimize the progress bar or move it around).
If there are any better solutions, I would love to see them. Hopefully, this has helped someone.
See Question&Answers more detail:
os