Update data in a Tkinter-GUI with data from a second Thread
This solution is based on comments from other person. It use queue.Queue
to share data between the two threads. The Tkinter GUI/Thread use a 1-second-timer to check if new data is in the Queue and use it to refresh its Labels.
#!/usr/bin/env python3# -*- coding: utf-8 -*-# based on <https://stackoverflow.com/a/47920128/4865723>from tkinter import *import asyncioimport threadingimport randomimport queueclass AsyncioThread(threading.Thread): def __init__(self, the_queue, max_data): self.asyncio_loop = asyncio.get_event_loop() self.the_queue = the_queue self.max_data = max_data threading.Thread.__init__(self) def run(self): self.asyncio_loop.run_until_complete(self.do_data()) async def do_data(self): """ Creating and starting 'maxData' asyncio-tasks. """ tasks = [ self.create_dummy_data(key) for key in range(self.max_data) ] await asyncio.wait(tasks) async def create_dummy_data(self, key): """ Create data and store it in the queue. """ sec = random.randint(1, 10) data = '{}:{}'.format(key, random.random()) await asyncio.sleep(sec) self.the_queue.put((key, data))class TheWindow: def __init__(self, max_data): # thread-safe data storage self.the_queue = queue.Queue() # the GUI main object self.root = Tk() # create the data variable self.data = [] for key in range(max_data): self.data.append(StringVar()) self.data[key].set('<default>') # Button to start the asyncio tasks Button(master=self.root, text='Start Asyncio Tasks', command=lambda: self.do_asyncio()).pack() # Frames to display data from the asyncio tasks for key in range(max_data): Label(master=self.root, textvariable=self.data[key]).pack() # Button to check if the GUI is freezed Button(master=self.root, text='Freezed???', command=self.do_freezed).pack() def refresh_data(self): """ """ # do nothing if the aysyncio thread is dead # and no more data in the queue if not self.thread.is_alive() and self.the_queue.empty(): return # refresh the GUI with new data from the queue while not self.the_queue.empty(): key, data = self.the_queue.get() self.data[key].set(data) print('RefreshData...') # timer to refresh the gui with data from the asyncio thread self.root.after(1000, self.refresh_data) # called only once! def do_freezed(self): """ Button-Event-Handler to see if a button on GUI works. The GOAL of this example is to make this button clickable while the other thread/asyncio-tasks are working. """ print('Tkinter is reacting. Thread-ID: {}' .format(threading.get_ident())) def do_asyncio(self): """ Button-Event-Handler starting the asyncio part in a separate thread. """ # create Thread object self.thread = AsyncioThread(self.the_queue, len(self.data)) # timer to refresh the gui with data from the asyncio thread self.root.after(1000, self.refresh_data) # called only once! # start the thread self.thread.start()if __name__ == '__main__': window = TheWindow(10) window.root.mainloop()
This example is based on https://stackoverflow.com/a/47920128/4865723.Not sure if this is an elegant solution. Please feel free to edit this. It is my goal to make my question and the answer reusable by others.
I solved it in another way.I'm not an expert, but I found this solution working for me. Any improvement is welcomed!
- Create the
Tk
object (usually calledroot
) in themain()
. - Create a thread to run your application (I assume it is a
Class
) in themain()
and passroot
to it as a parameter. - Execute
root.mainloop()
in themain()
- Define your GUI buttons, labels, entries, ... in the
__init__
of your application class - Update your GUI buttons, labels, entries, ... in your application class with Tkinter methods as usual (
get()
,set()
, ...)
This is a template to get the idea:
from tkinter import *import _threadclass App(threading.Thread): def __init__(self, root, my_param2, my_param3): # Creare GUI self.root = root # Your code here def method1(self): # Your code heredef main(): # Create GUI root = Tk(className='MyApp') # Create 2 threads num_threads = 2 for t in range(num_threads): try: _thread.start_new_thread(App, (root, my_param2, my_param3, ) ) except: print('Error: can not create a thread') # tkinter main loop root.mainloop() print ('You don\'t see this message')if __name__ == "__main__": main()