I'm trying to learn about object oriented python and using tkinter with threading. I've read a ton of SO questions and other various resources online. I've been able to work out most of it, but I'm a little lost on how to update a Treeview item from another thread. I have not been able to find something that illustrates this in a way I could understand.
I've read that tkinter must run in the main thread and you cannot update it from another thread, so I need to use a queue to pass things to it.
The following code works, although it's probably not the best way to do things. Any improvements are definitely welcomed, but the main thing I'm posting for is the Treeview question.
In the examples I see, people usually just use a random number generator or something to pass ints via the queue to demonstrate something simple. I understand this, but how can I use the queue to pass and run an actual command or otherwise update the Treeview item that sits in the main thread?
If not for being in another thread, I would use something like:
tv_files.item('File1', values=('tst1', 'tst2'))
import tkinter as tk
from tkinter import ttk
from threading import Thread
from queue import Queue
import time
class TkThread:
def __init__(self):
self.tk = tk.Tk()
self.message_queue = Queue()
self.message_event = '<<message>>'
self.tk.bind(self.message_event, self.process_message_queue)
def run_tk(self):
self.tk.title('My Window')
self.tv_files = ttk.Treeview(
self.tk,
columns=('Filename', 'Status'),
show='headings')
self.tv_files.pack(
side='top',
padx=10,
pady=10)
# Set up columns
self.tv_files.heading('#1', anchor='w', text='Filename')
self.tv_files.column('#1', anchor='w', width=150)
self.tv_files.heading('#2', anchor='w', text='Status')
self.tv_files.column('#2', anchor='w', width=150)
self.tv_files.insert('', 'end', id='File1', values=('File1', 'Status1'))
self.btn_run = ttk.Button(
self.tk,
text= 'Run',
compound='top',
command = run_thread)
self.btn_run.pack(
side='top',
padx=10,
pady=10)
self.tk.lift(); self.tk.mainloop()
def send_message_to_ui(self, message):
self.message_queue.put(message)
self.tk.event_generate(self.message_event, when='tail')
def process_message_queue(self, event):
while self.message_queue.empty() is False:
message = self.message_queue.get(block=False)
# process the message here
print(message)
def work():
time.sleep(3)
tk_thread.send_message_to_ui('Test Message')
def run_thread():
thread = Thread(target=work)
thread.start()
if __name__ == '__main__':
tk_thread = TkThread()
tk_thread.run_tk()
CodePudding user response:
You seem to have the basics down. Just add items to the queue that you can unpack and insert into the tree.
For example, you can add a dictionary onto the queue:
def work():
time.sleep(3)
message = {
"id": "File1",
"values": ('tst1', 'tst2')
}
tk_thread.send_message_to_ui(message)
In the event handler, you can pull the data out of the message and insert it into the tree:
def process_message_queue(self, event):
while self.message_queue.empty() is False:
message = self.message_queue.get(block=False)
self.tv_files.insert(message['id'], "end", values=message['values'])
If you want to get really fancy, you could pass the name of the function, args, and kwargs so that your thread can do just about anything in the GUI:
Sending the message:
def work():
time.sleep(3)
message = {
"attr": "tv_files",
"method": "insert",
"args": ("File1", "end"),
"kwargs": {
"values": ['tst1', 'tst2']
},
}
tk_thread.send_message_to_ui(message)
Processing the message:
def process_message_queue(self, event):
while self.message_queue.empty() is False:
message = self.message_queue.get(block=False)
attr = getattr(self, message['attr'])
method = getattr(attr, message['method'])
method(*message['args'], **message['kwargs'])