【问题标题】:tkinter: How to update Treeview item from another thread using queue?tkinter:如何使用队列从另一个线程更新 Treeview 项目?
【发布时间】:2021-12-30 21:33:01
【问题描述】:

我正在尝试学习面向对象的 python 并使用带有线程的 tkinter。我已经在线阅读了大量的 SO 问题和其他各种资源。我已经能够解决大部分问题,但是我对如何从另一个线程更新 Treeview 项目有点迷茫。我一直找不到能以我能理解的方式说明这一点的东西。

我读过 tkinter 必须在主线程中运行,你不能从另一个线程更新它,所以我需要使用一个队列来传递东西给它。

以下代码有效,尽管它可能不是最好的方法。任何改进都绝对受欢迎,但我要发布的主要内容是 Treeview 问题。

在我看到的示例中,人们通常只是使用随机数生成器或其他东西通过队列传递整数来演示一些简单的东西。我明白这一点,但是如何使用队列来传递和运行实际命令或以其他方式更新位于主线程中的 Treeview 项?

如果不是在另一个线程中,我会使用类似的东西: 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()

【问题讨论】:

    标签: python tkinter python-multithreading


    【解决方案1】:

    您似乎已经掌握了基础知识。只需将项目添加到您可以解包并插入到树中的队列中。

    例如,您可以将字典添加到队列中:

    def work():
      time.sleep(3)
      message = {
          "id": "File1",
          "values": ('tst1', 'tst2')
      }
      tk_thread.send_message_to_ui(message)
    

    在事件处理程序中,您可以将数据从消息中拉出并插入到树中:

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

    如果你想变得更花哨,你可以传递函数的名称、args 和 kwargs,这样你的线程就可以在 GUI 中做任何事情:

    发送消息:

    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)
    

    处理消息:

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

    【讨论】:

    • 我的大脑并没有把这些点连起来。我让它变得更难了。感谢您的帮助。
    • 再次感谢您对传递函数和参数的编辑。这肯定会派上用场。
    猜你喜欢
    • 1970-01-01
    • 2020-06-20
    • 1970-01-01
    • 2014-03-15
    • 1970-01-01
    • 2020-07-13
    相关资源
    最近更新 更多