Search code examples
pythonmultithreadingtkinterthread-safetypython-multithreading

Multithreading: tkinter mainloop not in main thread


I was wondering if there was a way to run the tkinter mainloop on a separate thread (not the mainthread) so that the terminal is "free".

Let's say we have a simple GUI:

import tkinter as tk       

class Application(tk.Frame):              
    def __init__(self, master=None):
        tk.Frame.__init__(self, master)   
        self.grid()                       
        self.createWidgets()

    def printHello(self):
        print("Hello")

    def createWidgets(self):
        self.quitButton = tk.Button(self, text='Quit',
            command=self.quit)            
        self.quitButton.grid(row=1,column=0)    
        self.printButton = tk.Button(self, text='Print',command=lambda: self.printHello())         
        self.printButton.grid(row=1,column=1) 
app = Application()                        
app.master.title('Sample application')     
app.mainloop()

Observed behavior: if I run this file using the terminal in PyCharm (let's say) using: python application.py, the terminal is then "frozen"/"occupied" by the tkinter mainloop and I am not able to type anything in the terminal.

Desired behavior: if I run the gui file with python application.py, the terminal is "free" such that it is able to take in additional commands.

I'm aware that python application.py & is supposed to free up the terminal but that hasn't worked for me. I'm not very experienced with threading or GUI processes but is there a way to run the tkinter mainloop in a different process / thread so that the terminal will be "free"?


Solution

  • You can use the threading module to run the GUI in a background thread while using the terminal in the main thread.

    Try this code. It will start the GUI then allow input from the terminal.

    import tkinter as tk       
    import threading
    
    class Application(tk.Frame):              
        def __init__(self, master=None):
            tk.Frame.__init__(self, master)   
            self.grid()                       
            self.createWidgets()
    
        def printHello(self):
            print("Hello")
    
        def createWidgets(self):
            self.quitButton = tk.Button(self, text='Quit',
                command=self.quit) # exits background (gui) thread
            self.quitButton.grid(row=1,column=0)    
            self.printButton = tk.Button(self, text='Print',command=lambda: self.printHello())         
            self.printButton.grid(row=1,column=1) 
    
    def runtk():  # runs in background thread
        app = Application()                        
        app.master.title('Sample application')     
        app.mainloop()
        
    thd = threading.Thread(target=runtk)   # gui thread
    thd.daemon = True  # background thread will exit if main thread exits
    thd.start()  # start tk loop
    
    while True:  # run in main thread
       x = input("Enter a value or Q to quit: ")
       if x.lower() == 'q':
          exit()
       print('You entered', x)