Search code examples
pythontkinterpython-multithreading

Tkinter close protocol when tkinter is threaded


I have this small code that execute a function and in the meanwhile shows an indeterminate tkinter progressbar. When you let the program execute everything works fine while if you try to stop the process by closing the tkinter window you get the RuntimeError: main thread is not in main loop. I understand that the solution could be to bring the bar mainloop in the main thread or alternatively use queue but it is not clear to me how to do this. Here you can find the code with a simple mock function (addit) that executes. Thank you everyone in advance!

import threading
importing tkinter module
import tkinter
import tkinter.ttk as ttk
from tkinter import messagebox
from tkinter import Button
from tkinter import Label
import sys
import time



class tkwindow(threading.Thread):


    def __init__(self):
        threading.Thread.__init__(self, daemon=True)

        
        
    def run(self):
        self.wbar=tkinter.Tk()
        self.wbar.attributes("-topmost", True) 
        self.wbar.title('Tool')
        lb=Label(self.wbar,text='Check in progress')
        lb.pack()
        pbar = ttk.Progressbar(self.wbar,orient='horizontal', length=500, mode='indeterminate')
        pbar.pack(pady=25)
        pbar.start(50) 
        self.wbar.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.loopmain()
        
    def loopmain(self):
        
        self.wbar.mainloop()
        
               
        
    def quitall(self):
        
        self.wbar.quit()
        sys.exit()
      
    def on_closing(self):
        if messagebox.askokcancel("Quit", "Do you want to quit?"):
          self.wbar.quit()
          sys.exit()   
        
       

def main(): 

    mygui=tkwindow()     
    mygui.start()
    addit(2,3)
    
    mygui.quitall()
         


def addit(a,b):
    time.sleep(3)
    print(a+b)
    return


if __name__=='__main__':  
    main()
     

Solution

  • Probably I have found a way to do just what I wanted using queue. Unfortunately the code kill the main thread abruptly with interrupt_main and not in a nice way as sys.exit() could do but is the only solution I have found for now. The updated code is the following:

    import threading
    import tkinter
    import tkinter.ttk as ttk
    from tkinter import messagebox
    from tkinter import Button
    from tkinter import Label
    import sys
    import time
    from queue import Queue
    import _thread
    import os
    
    
    
    class tkwindow(threading.Thread):
    
    
        def __init__(self,dataq):
            threading.Thread.__init__(self, daemon=True)
            self.dataq=dataq
    
            
            
        def run(self):
            self.wbar=tkinter.Tk()
            self.wbar.attributes("-topmost", True) 
            self.wbar.title('Tool')
            lb=Label(self.wbar,text='Check in progress')
            lb.pack()
            pbar = ttk.Progressbar(self.wbar,orient='horizontal', length=500, mode='indeterminate')
            pbar.pack(pady=25)
            pbar.start(50) 
            self.dataq.put(0)
            self.loopmain()
            
        def loopmain(self):
            self.wbar.protocol("WM_DELETE_WINDOW", self.on_closing)
            self.wbar.after(100,self.checkq)
            self.wbar.mainloop()
            
            
        def checkq(self):
            
            v=self.dataq.get()
            if v:
              if messagebox.askokcancel("Quit", "Do you want to quit?"):
                self.wbar.quit()
                os._exit(0)  #or alternatively _thread.interrupt_main()
                
             
            
        def quitall(self):
            
            self.wbar.quit()
            sys.exit()
    
          
        def on_closing(self):
            self.dataq.put(1)
            self.checkq()
    
    
    
    
    def main(): 
        dataq=Queue()
        mygui=tkwindow(dataq)     
        mygui.start()
        addit(2,3)   
        mygui.quitall()
        
    
    
    def addit(a,b):
    
        time.sleep(3)
        print(a+b)
        return
    
        
       
    
    if __name__=='__main__':  
        main()