Update data in a Tkinter-GUI with data from a second Thread Update data in a Tkinter-GUI with data from a second Thread tkinter tkinter

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!

  1. Create the Tk object (usually called root) in the main().
  2. Create a thread to run your application (I assume it is a Class) in the main() and pass root to it as a parameter.
  3. Execute root.mainloop() in the main()
  4. Define your GUI buttons, labels, entries, ... in the __init__ of your application class
  5. 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()