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