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:
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).
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.
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.
You can create a function that just uses pass
to do nothing.
Take a look at the below:
import tkinter as tk
root=tk.Tk()
def close_program():
root.destroy()
def disable_event():
pass
btn = tk.Button(root, text = "Click me to close", command = close_program)
btn.pack()
root.protocol("WM_DELETE_WINDOW", disable_event)
root.mainloop()
You could also remove the toolbar all together with root.overrideredirect(True)
that will prevent the user from using any of the toolbar. leaving root.protocol("WM_DELETE_WINDOW", disable_event)
will also prevent the use of ALT + F4
.
import tkinter as tk
root=tk.Tk()
root.geometry("400x400")
root.overrideredirect(True)
def close_program():
root.destroy()
def disable_event():
pass
btn = tk.Button(root, text = "Click me to close", command = close_program)
btn.pack()
root.protocol("WM_DELETE_WINDOW", disable_event)
root.mainloop()
another way to achieve this on windows:
#!python3
import tkinter as tk
from tkinter import ttk
import threading, time
import tkinter as tk
from ctypes import windll, wintypes
GWL_STYLE = -16
WS_CHILD = 0x40000000
WS_SYSMENU = 0x00080000
SWP_FRAMECHANGED = 0x0020
SWP_NOACTIVATE = 0x0010
SWP_NOMOVE = 0x0002
SWP_NOSIZE = 0x0001
# write short names for functions and specify argument and return types
GetWindowLong = windll.user32.GetWindowLongW
GetWindowLong.restype = wintypes.ULONG
GetWindowLong.argtpes = (wintypes.HWND, wintypes.INT)
SetWindowLong = windll.user32.SetWindowLongW
SetWindowLong.restype = wintypes.ULONG
SetWindowLong.argtpes = (wintypes.HWND, wintypes.INT, wintypes.ULONG)
SetWindowPos = windll.user32.SetWindowPos
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.pb = ttk.Progressbar(self, orient="horizontal", length=400, mode="determinate", maximum=100)
self.pb.pack()
tk.Button(self, text="Remove buttons", command=self.remove_buttons).pack()
tk.Button(self, text="Add buttons", command=self.add_buttons).pack()
def start(self):
self.t = threading.Thread(target=self.loop)
self.t.start()
def loop(self):
while True:
for num in range(0, 100):
self.pb['value']=num
time.sleep(0.1)
def _get_hwnd(self):
w_id = self.winfo_id() # gets handle
style = GetWindowLong(w_id, GWL_STYLE) # get existing style
newstyle = style & ~WS_CHILD # remove child style
res = SetWindowLong(w_id, GWL_STYLE, newstyle) # set new style
res = SetWindowPos(w_id, 0, 0,0,0,0, SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE)
hwnd = int(self.wm_frame(), 16) # find handle of parent
res = SetWindowLong(w_id, GWL_STYLE, style) # set back to old style
res = SetWindowPos(w_id, 0, 0,0,0,0, SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE)
return hwnd # return parents handle
def remove_buttons(self):
hwnd = self._get_hwnd()
style = GetWindowLong(hwnd, GWL_STYLE) # get existing style
style = style & ~WS_SYSMENU
res = SetWindowLong(hwnd, GWL_STYLE, style)
res = SetWindowPos(hwnd, 0, 0,0,0,0, SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE)
def add_buttons(self):
hwnd = self._get_hwnd()
style = GetWindowLong(hwnd, GWL_STYLE) # get existing style
style = style | WS_SYSMENU
res = SetWindowLong(hwnd, GWL_STYLE, style)
res = SetWindowPos(hwnd, 0, 0,0,0,0, SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE)
if __name__ == "__main__":
app = App()
app.start()
app.mainloop()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With