Reputation: 33
I'm trying to pack something in Tkinter from a separate thread. I keep getting a RuntimeError
.
Specifically: RuntimeError: main thread is not in main loop
The question is: How do I update the GUI (mainloop) from a separate thread?
The source of the issue:
def protocol(self, scheduleName): #ran as thread
print("Getting courses")
self.coursesLabel.pack() #throws the error
...
I want to be able to pack items from this thread to mainloop()
. I'm not sure how to transfer this between two threads.
Thanks in advance for the help!
EDIT: Most of these questions that already exist respond to people who are running two separate GUIs simultaneously. I just need to know how to pack things from one thread to another, not something as complex as running two GUIs at the same time. Most answers also used Queues, and I was wondering if there was a more elegant way to pack things from separate threads.
Upvotes: 3
Views: 7202
Reputation: 11342
In tkinter, you can submit an event from a background thread to the GUI thread using event_generate. This allows you to update widgets without threading errors.
event_generate
to trigger the event in the main thread. Use the state
property to pass data (number) to the event.Here's an example:
from tkinter import *
import datetime
import threading
import time
root = Tk()
root.title("Thread Test")
print('Main Thread', threading.get_ident()) # main thread id
def timecnt(): # runs in background thread
print('Timer Thread',threading.get_ident()) # background thread id
for x in range(10):
root.event_generate("<<event1>>", when="tail", state=123) # trigger event in main thread
txtvar.set(' '*15 + str(x)) # update text entry from background thread
time.sleep(1) # one second
def eventhandler(evt): # runs in main thread
print('Event Thread',threading.get_ident()) # event thread id (same as main)
print(evt.state) # 123, data from event
string = datetime.datetime.now().strftime('%I:%M:%S %p')
lbl.config(text=string) # update widget
#txtvar.set(' '*15 + str(evt.state)) # update text entry in main thread
lbl = Label(root, text='Start') # label in main thread
lbl.place(x=0, y=0, relwidth=1, relheight=.5)
txtvar = StringVar() # var for text entry
txt = Entry(root, textvariable=txtvar) # in main thread
txt.place(relx = 0.5, rely = 0.75, relwidth=.5, anchor = CENTER)
thd = threading.Thread(target=timecnt) # timer thread
thd.daemon = True
thd.start() # start timer loop
root.bind("<<event1>>", eventhandler) # event triggered by background thread
root.mainloop()
thd.join() # not needed
Output (note that the main and event threads are the same)
Main Thread 5348
Timer Thread 33016
Event Thread 5348
......
I added an Entry widget to test if the StringVar
can be updated from the background thread. It worked for me, but you can update the string in the event handler if you prefer. Note that updating the string from multiple background threads could be a problem and a thread lock should be used.
Note that if the background threads exits on its own, there is no error. If you close the application before it exits, you will see the 'main thread' error.
Upvotes: 4