简体   繁体   中英

tkinter: How to update Treeview item from another thread using queue?

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()

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'])

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM