I have plan to make a simple test with tkinter which will change the left text with new input. But it seems threading is not easy as my thought. It still be hang after 2 times of input.
from tkinter import *
import threading
win = Tk()
label1 = Label(win, text="this is a test on the left")
label1.pack(side=LEFT)
label2 = Label(win, text="this is a test on the right")
label2.pack(side=RIGHT)
def set_text():
while(True):
content=input("let enter the substuition:")
label1.config(text = content)
win.after(100, set_text)
setTextthr=threading.Thread(target = set_text)
setTextthr.start()
win.mainloop()
It is very impressed if you can point out why it happened and how to fix. Thanks
CodePudding user response:
There are some unwritten rules about threading in combination with tkinter. One of them is not to touch any widget outside of the thread of tkinter. This line violates this rule.
label1.config(text = content)
In order to do this you can use a tkinter.StingVar
which isnt directly in the main event loop of tkinter.
The following line also violates the rule since it referece to the instance of tk.Tk().
win.after(100, set_text)
In addition, the call from inside the function to itself after
some time would create a loop that you already have built with a whileloop
. Its therefor unnecessary and wrong at the same time. Below you find a working exampel which has its limits. For another approache you can take a look at this or this.
from tkinter import *
import threading
win = Tk()
var = StringVar(value="this is a test on the left")
label1 = Label(win,textvariable=var)
label1.pack(side=LEFT)
label2 = Label(win, text="this is a test on the right")
label2.pack(side=RIGHT)
def set_text():
while(True):
content=input("let enter the substuition:")
var.set(content)
setTextthr=threading.Thread(target = set_text)
setTextthr.start()
win.mainloop()
CodePudding user response:
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()