Jun-07-2020, 04:58 PM
(This post was last modified: Jun-07-2020, 06:48 PM by Gribouillis.)
You cannot use threads that easily to update a widget's contents in tkinter because the widgets must be updated from the thread that is running the mainloop().
I give you below a complete example of what I mean with the
At the other end of the queue, there is a generator that extracts items from the queue in a non blocking way (so possiby no items at all) and updates the text widget with the data that it receives. This code is run by the main thread. I'm using a trick here: after trying to extract data from the queue, the function runs the "yield" statement, thus becoming a generator. The GUI will use my custom PeriodicConsumer class to drive this generator by intervals of 500 milliseconds. This is done with Tkinter's
It is a complete example, so you can run it and try to understand it.
I give you below a complete example of what I mean with the
after()
method. In this example, a "producer thread" is started that reads data in a file and puts this data in a queue at a random speed.At the other end of the queue, there is a generator that extracts items from the queue in a non blocking way (so possiby no items at all) and updates the text widget with the data that it receives. This code is run by the main thread. I'm using a trick here: after trying to extract data from the queue, the function runs the "yield" statement, thus becoming a generator. The GUI will use my custom PeriodicConsumer class to drive this generator by intervals of 500 milliseconds. This is done with Tkinter's
after()
method.It is a complete example, so you can run it and try to understand it.
import threading class Error(RuntimeError): pass class PeriodicConsumer: def __init__(self, widget): self.master = widget self._job = None self._stopped = threading.Event() self._stopped.set() self._seq = None def start(self, sequence, millisecs): self.ms = millisecs if self._stopped.is_set(): self._stopped.clear() self._seq = sequence self._job = self.master.after(self.ms, func=self._step) else: raise Error('Expected AfterThread in stopped state') def stop(self): if self._stopped.is_set(): return self._stopped.set() if self._job and ( threading.current_thread() is threading.main_thread()): self.master.after_cancel(self._job) self._job = None def _step(self): if self._stopped.is_set(): return try: next(self._seq) except StopIteration: self.stop() else: self._job = self.master.after(self.ms, func=self._step) if __name__ == '__main__': import io import queue import random import time import tkinter as tk root = tk.Tk() text = tk.Text(root) text.pack() pc = PeriodicConsumer(root) def update_text_from_queue(aqueue): while True: result = [] try: while True: x = aqueue.get_nowait() if x is None: # None in the queue is our signal for # end of data. if result: text.insert(tk.END, "".join(result)) return else: result.append(x) except queue.Empty: pass if result: text.insert(tk.END, "".join(result)) yield myqueue = queue.Queue() pc.start(update_text_from_queue(myqueue), 500) source_file = io.StringIO("""\ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin commodo turpis ex, commodo pretium nunc tincidunt vel. Maecenas quis mi ac ligula sagittis placerat ut ac justo. Fusce nec sodales tortor. Duis hendrerit leo at odio faucibus. """) def produce(aqueue): for line in source_file: aqueue.put(line) time.sleep(random.random()) aqueue.put(None) # our convention for end of input producer = threading.Thread(target=produce, args=(myqueue,)) producer.start() root.mainloop()