Can somebody help me out with threading in python and getting a progress bar working?
Even research gives quite a lot results, i cannot get it working.
I never did threading before and i cannot tell where to put things correctly.
For testing purposes i prepared a simple GUI with a button and a progress bar:
After clicking the button a simple 3d plot will pop-up.
Such plotting may take some computation time and while a user need to wait, id like to have the GUI not frozen and the progressbar animated.
At the moment the GUI freezes until the plot will show up. And after that the progress bar start the animation.
I have read a lot about threading and to put calculations and gui to different threads? But im just to noobish to get it working. Is somebody able to explain me more, direct me to similar problems or documentations? Or maybe, in case is quickly solved, overtake the simple code and correct it the way it should be?
Thanks in advance for any kind of help.
Python script so far:
from time import sleep
from tkinter import EW
import ttkbootstrap as ttk
import numpy as np
import matplotlib.pyplot as plt
def main():
def plot_sample():
sleep(5) # simulate calculation times
x = np.outer(np.linspace(-2, 2, 30), np.ones(30))
y = x.copy().T # transpose
z = np.cos(x ** 2 y ** 2)
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.plot_surface(x, y, z,cmap='viridis', edgecolor='none')
ax.set_title('Surface plot')
plt.show()
def progressbar_start():
progressbar.grid(row=1, column=0, sticky=EW, padx=10, pady=10) # let progressbar appear in GUI
progressbar.start(interval=10)
def progressbar_stop():
progressbar.stop()
progressbar.grid_forget() # hide progressbar when not needed
def button_clicked():
progressbar_start() # start progressbar before computation begins
plot_sample() # plotting
progressbar_stop() # stop progressbar after plot is done
# GUI
# window size and settings
root = ttk.Window()
# Basic settings for the main window
root.title('Test progressbar')
root.minsize(300, 200)
root.resizable(True, True)
root.configure(bg='white')
# grid settings for the main window in which LabelFrames are sitting in
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
root.rowconfigure(1, weight=1)
# Button fto show 3d-plot
button_calc_3dplot = ttk.Button(root, text='Calculate 3D Plot', command=button_clicked)
button_calc_3dplot.grid(row=0, column=0, padx=5, pady=5)
progressbar = ttk.Progressbar(style='success-striped', mode='indeterminate')
# end of GUI
root.mainloop()
if __name__ == "__main__":
main()
CodePudding user response:
I have one solution. The main problem is, that You can't run the plt.show() function in a Thread. From this follows, that you can't run the window and the plt.show function at the same time.
from time import sleep
from tkinter import EW
import ttkbootstrap as ttk
import numpy as np
import matplotlib.pyplot as plt
from threading import Thread
x, y, z = None, None, None
def main():
def calculate_xyz():
sleep(5) # simulate calculation times
global x, y, z
x = np.outer(np.linspace(-2, 2, 30), np.ones(30))
y = x.copy().T # transpose
z = np.cos(x ** 2 y ** 2)
def progressbar_start():
progressbar.grid(row=1, column=0, sticky=EW, padx=10, pady=10) # let progressbar appear in GUI
progressbar.start(interval=10)
def progressbar_stop():
progressbar.stop()
progressbar.grid_forget() # hide progressbar when not needed
def button_clicked():
progressbar_start() # start progressbar before computation begins
calculate_xyz_thread = Thread(target=calculate_xyz)
calculate_xyz_thread.start() # calculate x, y and z
root.after(500, wait_for_plotting) # wait for plotting and stop animation after finishing
def wait_for_plotting():
if x is None or y is None or z is None:
root.after(500, wait_for_plotting) # run self again
return # wait for calculation
# calculation is finished
progressbar_stop() # stop progressbar "after" plot is done
ax = plt.axes(projection='3d')
ax.plot_surface(x, y, z, cmap='viridis', edgecolor='none')
ax.set_title('Surface plot')
root.after(500, plot_3d)
def plot_3d():
plt.show()
# GUI
# window size and settings
root = ttk.Window()
# Basic settings for the main window
root.title('Test progressbar')
root.minsize(300, 200)
root.resizable(True, True)
root.configure(bg='white')
# grid settings for the main window in which LabelFrames are sitting in
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
root.rowconfigure(1, weight=1)
# Button fto show 3d-plot
button_calc_3dplot = ttk.Button(root, text='Calculate 3D Plot', command=button_clicked)
button_calc_3dplot.grid(row=0, column=0, padx=5, pady=5)
progressbar = ttk.Progressbar(style='success-striped', mode='indeterminate')
# end of GUI
root.mainloop()
if __name__ == "__main__":
main()
CodePudding user response:
You should create a separate thread that will run the function plot_sample and update the progress bar. To do this, you will need to import the threading module.
import threading
def main():
def plot_sample():
# your code to create the plot
...
def progressbar_start():
progressbar.grid(row=1, column=0, sticky=EW, padx=10, pady=10)
progressbar.start(interval=10)
def progressbar_stop():
progressbar.stop()
progressbar.grid_forget()
def button_clicked():
progressbar_start()
# create a new thread to run the plot_sample function
t = threading.Thread(target=plot_sample)
# start the thread
t.start()
# stop the progress bar after the thread finishes
t.join()
progressbar_stop()
Now, when the button is clicked, the progress bar will start animating and the plot_sample function will be run in a separate thread. This will allow the GUI to remain responsive while the plot is being created.