How to Organize Threaded GUI Application (Python) How to Organize Threaded GUI Application (Python) tkinter tkinter

How to Organize Threaded GUI Application (Python)


The short answer is, you can't interact with widgets from worker threads. Your only choice is to have your worker threads push something on a thread-safe queue, and have the main thread poll it.

You don't need any while loops to poll the queue. You already have an infinite loop -- the event loop (eg: mainloop) -- so no need to add an extra one.

The way to poll the queue from the main thread looks something like this:

def pollQueue(self):    <look at the queue, act on the results>    self.after(100, self.pollQueue)

What this does is arranges to poll the queue every 100 ms. You can, of course, set the polling interval to whatever you want.


Instead of using threading you should be using the tkinter method "after" to setup an event on the tkinter event loop.

for example when using a canvas element I would use

canvar.after(50, func=keepDoingSomething)

this works similar to the javascript function setTimeout and it is thread safe and wont interfere with tkinter gui threads.


I think it would be better to create 3 classes instead of 2 and divide them into

  • GUI
  • Functions
  • App

The GUI and function are pretty self descriptive, the app is a bridge between the two, so that their work doesn't get hindered.

A sample working code is this-

import tkinter as tkfrom tkinter import ttk,messageboximport threadingimport time#base GUI Classclass GUI:    def __init__(self, root, runCommand):        mf = ttk.Frame(root, padding="5 5 5 5")        mf.grid(column=0, row=0)        mf.columnconfigure(0, weight=1)        mf.rowconfigure(0, weight=1)        # Global Values        self.Fnm = tk.StringVar(root, "SearchFile.xlsx")        self.Ncol = tk.StringVar(root, "D")        self.Vcol = tk.StringVar(root, "C")        # Label        tk.Label(mf, text="File Name").grid(column=1, row=1, pady=6)        tk.Label(mf, text="Name Col").grid(column=1, row=3, pady=6)        tk.Label(mf, text="Value Col").grid(column=3, row=3, pady=6)        # components        self.fname = ttk.Entry(mf, width=18, textvariable=self.Fnm)        self.nmCol = ttk.Entry(mf, width=6, textvariable=self.Ncol)        self.valCol = ttk.Entry(mf, width=6, textvariable=self.Vcol)        self.but = ttk.Button(mf, text="Refresh", command=runCommand)        self.pgbar = ttk.Progressbar(mf, orient="horizontal", mode="determinate")        # Design        self.fname.grid(column=2, row=1, pady=3, columnspan=3)        self.nmCol.grid(column=2, row=3, pady=3)        self.valCol.grid(column=4, row=3, pady=3)        self.but.grid(column=2, row=2, columnspan=2)        self.pgbar.grid(column=1,row=4,columnspan=4)    def refresh(self):        pass    def get(self):        return [self.Fnm.get(), self.Ncol.get(), self.Vcol.get()]#Base process Classclass Proc:    def __init__(self, dets,pgbar,but):        self.Fnm = dets[0]        self.Ncol = dets[1]        self.Vcol = dets[2]        self.pg=pgbar        self.butt=but    def refresh(self):        self.butt['state'] = 'disabled'        self.pg.start()        #ATTENTION:Enter Your process Code HERE        for _ in range(5):            time.sleep(2)        self.pg.stop()        #Any search/sort algorithm to be used        #You can use self.pg.step() to be more specific for how the progress bar proceeds        messagebox.showinfo("Process Done","Success")        self.butt['state'] = 'enabled'#Base Application Classclass App:    def __init__(self, master):        self.master = master        self.gui = GUI(self.master, self.runit)    def runit(self):        self.search = Proc(self.gui.get(),self.gui.pgbar,self.gui.but)        self.thread1 = threading.Thread(target=self.search.refresh)        self.thread1.start()def main():    app = tk.Tk()    gui = App(app)    app.title("Refresh Search File")    app.mainloop()if __name__ == '__main__':    main()