Home > database >  Tkinter close protocol when tkinter is threaded
Tkinter close protocol when tkinter is threaded

Time:10-12

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()
     

CodePudding user response:

The structure of the code is only wrong. You should inherit tk.Tk and not threading.Thread. Instead of creating a function for the mainloop, just insert it at the bottom of the run() function (use self instead of self.wbar). Initialize the thread at the start of the run() function. In the main() function you have called start but in the class you defined it as run(). Here is your code if you added the above changes and fixed all errors:

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

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

def main(): 
    mygui=tkwindow()     
    mygui.run()
    addit(2,3)
    
    mygui.quitall()

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

if __name__=='__main__':  
    main()

There is no way to put the progress bar into the thread. Even if you could, it wouldn't be of any use since it would make the code very complex. So just use this as an alternative.

Updated Code

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

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

def main(): 
    mygui=tkwindow()     
    mygui.run()
    addit(2,3)
    
    mygui.mainloop()
    
def addit(a,b):
    print("ADDIT")
    threading.Timer(3, lambda: print(a b)).start()
    
if __name__=='__main__':  
    main()

CodePudding user response:

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()
     
  • Related