Reputation: 41
My question is regarding initializing a GUI built with tkinter. I'm starting another thread within the GUI that's running a script which takes 10 minutes to finish. The reason I'm doing this in another thread is to be able to keep the GUI responsive during these 10 minutes.
Right now I'm trying to do the following (simplified)
import tkinter as tk
from threading import Thread
class GUI:
def __init__(self):
self.master = tk.Tk()
self.master.geometry("1400x700")
//
...
//
self.master.mainloop()
def run_long_script(self): # Called by button in GUI
self.t1 = Thread(target = long_script)
self.start()
def long_script(self):
try:
...
except InterruptedError as error:
GUI()
This works okay, but when I try to close the GUI with long_script
running, I get the error message main thread not in main loop
. How should I design the code to be able to close the program correctly?
Upvotes: 0
Views: 1028
Reputation: 142793
Tkinter
like many other GUIs can use widgets only in main thread
(or rather in thread which runs mainlooop
).
Other thread has to update global value (which is shared between threads) or use queue
to send value to main thread. And main thread has to use after(milliseconds, function_name)
to run periodically function which will get value from global variable or from queue
and update progress bar.
Minimal working code.
import threading
import time
import tkinter as tk
import tkinter.ttk as ttk
# --- functions ---
def long_script():
global progress_value
for i in range(20):
print('loop:', i)
# update global variable
progress_value += 5
time.sleep(.5)
def run_long_script():
global progress_value
global t
if t is None: # run only one thread
# set start value
progress_value = 0
# start updating progressbar
update_progressbar()
# start thread
t = threading.Thread(target=long_script)
t.start()
else:
print('Already running')
def update_progressbar():
global t
# update progressbar
pb['value'] = progress_value
if progress_value < 100:
# run it again after 100ms
root.after(100, update_progressbar)
else:
# set None so it can run thread again
t = None
# --- main ---
# default value at start
progress_value = 0
t = None
# - gui -
root = tk.Tk()
pb = ttk.Progressbar(root, mode="determinate")
pb.pack()
b = tk.Button(root, text="start", command=run_long_script)
b.pack()
root.mainloop()
Upvotes: 1