Kindt
Kindt

Reputation: 41

Initializing tkinter gui leads to main thread not in main loop

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

Answers (1)

furas
furas

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

Related Questions