I made a GUI Python program with Tkinter. I made a progressbar using "from tkinter.ttk import Progressbar". When I press a button, I apply a function that do something and return a Boolean value. I need that the progressbar will run until that function ends the process, and after that it will stop.
from tkinter.ttk import Progressbar
import time
import threading
wheel_flag = False
root = tk.Tk()
wheel = Progressbar(row,orient=HORIZONTAL,length=100,mode="indeterminate")
def btn_function()
loading_function = threading.Thread(target=start_loading)
loading_function.start()
boolean_wheel = threading.Thread(target=some_function, args = (x,y)) #"some_function" returns a boolean value
boolean_wheel.start()
while True:
if not boolean_wheel.is_alive():
break
wheel_flag = True
def start_loading():
while True:
global wheel_flag
wheel['value'] = 10
root.update_idletasks()
time.sleep(0.5)
if wheel_flag:
break
Here I don't get the boolean_wheel value, but I want to check if it true or false and send to the user message if function succeeded or not.
I want that when "btn_function" is applied, the progressbarwill start to load until "some_function" will finish run. Now the result I get is that the loading start only after "some_function" finished, and runs without stopping until I close the program.
CodePudding user response:
I would use root.after(millisecond, function)
instead of while True
-loop to run function which updates progressbar
every few seconds. And I would use directly boolean_wheel.is_alive()
in this function to stop updateing.
import tkinter as tk
from tkinter import ttk
import time
import threading
# --- functions ---
def some_function(x, y):
time.sleep(10)
def btn_function():
x = 100
y = 100
boolean_wheel = threading.Thread(target=some_function, args=(x,y))
boolean_wheel.start()
# block button
button['state'] = 'disabled'
# show progressbar
wheel.grid(row=0, column=1)
# start updating progressbar
update_progressbar(boolean_wheel) # send thread as parameter - so it doesn't need `global`
def update_progressbar(thread):
if thread.is_alive():
# update progressbar
wheel['value'] = 10
# check again after 250ms (0.25s)
root.after(250, update_progressbar, thread)
else:
# hide progressbar
wheel.grid_forget()
# unblock button
button['state'] = 'normal'
# --- main ---
root = tk.Tk()
wheel = ttk.Progressbar(root, orient='horizontal', length=100, mode="indeterminate")
#wheel.grid(row=0, column=1) # don't show
button = tk.Button(root, text='Start', command=btn_function)
button.grid(row=0, column=0)
root.mainloop()
EDIT:
If you would send widgets to functions as parameters then you could run it for many progressbars
import tkinter as tk
from tkinter import ttk
import time
import threading
# --- functions ---
def some_function(x, y):
time.sleep(10)
def btn_function(row, button, progressbar):
x = 100
y = 100
boolean_wheel = threading.Thread(target=some_function, args=(x,y))
boolean_wheel.start()
start_loading(row, button, progressbar, boolean_wheel)
def start_loading(row, button, progressbar, thread):
# block button
button['state'] = 'disabled'
# show progressbar
progressbar.grid(row=row, column=1)
# start updating progressbar
update_progressbar(button, progressbar, thread) # send thread as parameter - so it doesn't need `global`
def update_progressbar(button, progressbar, thread):
if thread.is_alive():
# update progressbar
progressbar['value'] = 10
# check again after 250ms (0.25s)
root.after(250, update_progressbar, button, progressbar, thread)
else:
end_loading(button, progressbar)
def end_loading(button, progressbar):
# hide progressbar
progressbar.grid_forget()
# unblock button
button['state'] = 'normal'
# --- main ---
root = tk.Tk()
# ---
wheel1 = ttk.Progressbar(root, orient='horizontal', length=100, mode="indeterminate")
#wheel.grid(row=1, column=1) # don't show
button1 = tk.Button(root, text='Start')
button1.grid(row=1, column=0)
button1['command'] = lambda:btn_function(1, button1, wheel1) # need to assign after creating button - to send button to function
# ---
wheel2 = ttk.Progressbar(root, orient='horizontal', length=100, mode="indeterminate")
#wheel2.grid(row=2, column=1) # don't show
button2 = tk.Button(root, text='Start')
button2.grid(row=2, column=0)
button2['command'] = lambda:btn_function(2, button2, wheel2) # need to assign after creating button - to send button to function
# ---
root.mainloop()
I would even put it in Frame
and create own widget class MyProgress(tk.Frame)
to create many widgets using for
-loop
import tkinter as tk
from tkinter import ttk
import time
import threading
# --- classes ---
class MyProgrees(tk.Frame):
def __init__(self, master, target_function, target_args, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.target_function = target_function
self.target_args = target_args
self.progressbar = ttk.Progressbar(self, orient='horizontal', length=100, mode="indeterminate")
#self.progressbar.grid(row=0, column=1) # don't show
self.button = tk.Button(self, text='Start', command=self.on_press_button)
self.button.grid(row=0, column=0)
def on_press_button(self):
self.thread = threading.Thread(target=self.target_function, args=self.target_args)
self.thread.start()
self.start_loading()
def start_loading(self):
# block button
self.button['state'] = 'disabled'
# show progressbar
self.progressbar.grid(row=0, column=1)
# start updating progressbar
self.update_progressbar()
def update_progressbar(self):
if self.thread.is_alive():
# update progressbar
self.progressbar['value'] = 10
# check again after 250ms (0.25s)
self.after(250, self.update_progressbar)
else:
self.end_loading()
def end_loading(self):
# hide progressbar
self.progressbar.grid_forget()
# unblock button
self.button['state'] = 'normal'
# --- functions ---
def some_function(x, y):
time.sleep(10)
# --- main ---
root = tk.Tk()
# ---
x = 100
y = 100
for _ in range(10):
widget = MyProgrees(root, some_function, (x, y))
widget.pack(fill='x', expand=True)
# ---
root.mainloop()
CodePudding user response:
the proper solution is to remove the loop in btn_function
and instead have some_function
set wheel_flag
after it is done.
def some_function_wrapper(*args,**kwargs):
global wheel_flag
some_function(*args,**kwargs)
wheel_flag = True
and call this instead of some_function
inside your thread.
now another solution is to just run the entire btn_function
inside another thread, by adding
command= lambda : threading.Thread(target=btn_function).start()
inside your button definition, but nesting threads sometimes becomes hard to track.
Edited: It works. I checked the function value inside that function
def some_function_wrapper(*args,**kwargs):
global wheel_flag
check = some_function(*args,**kwargs)
if check:
print("works")
else:
print("don't work")
wheel_flag = True