The usual way to approach getting data from threads in an event-driven program (as it is with tkinter
) is to use some update loop (.after
) to schedule a check on the data container (queue.Queue
or a simple list
) that contains data from the thread. Then additionally check if the thread is alive at all, and if it is not you can stop the update loop because there is nothing to update anymore. Also use some flag (threading.Event
) to stop the thread loop upon destroying the window. The only issue is that input
is used which makes it so that at the end at least the Enter key has to be pressed.
The code example (explanation in code comments):
import tkinter as tk
import threading
import queue
# this is the function that will run in another thread
def ask_input(q): # argument is the queue
# here check if the event is set, if it is
# don't loop anymore, however it will still wait
# for input so that has to be entered and
# then the thread will stop
while not event.is_set():
user_input = input('New content: ')
# put the user entered data into the queue
q.put(user_input)
# the function that will update the label
def update_label(q, t): # pass in the the queue and thread
# this loop simply gets the last item in the queue
# otherwise (not in this case perhaps) it may be a little
# too slow in updating since it only runs every 100 ms
# so it need to catch up
while not q.empty():
# get data from the queue
text = q.get()
# here it is save to update the label since it is the same
# thread where all of the tkinter runs
label.config(text=text)
# check if thread still runs (in this case this specific check
# is unnecessary since the thread is stopped only after
# the main window is closed)
if t.is_alive():
# schedule the next call with the same queue and thread
root.after(100, update_label, q, t)
# the function that initiates the thread
def start_thread():
# create a queue to pass as the argument to the thread and updater
_queue = queue.Queue()
# create and start a thread
thread = threading.Thread(target=ask_input, args=(_queue,))
thread.start()
# start updating the label
update_label(_queue, thread)
# check if the code is run without importing
if __name__ == '__main__':
root = tk.Tk()
# set this attribute so that the window always stays on top
# so that you can immediately see the changes in the label
root.attributes('-topmost', True)
# create the event
event = threading.Event()
# if window is destroyed set event which will break the loop
root.bind('<Destroy>', lambda _: event.set())
label = tk.Label(root)
label.pack()
start_thread()
root.mainloop()