【问题标题】:How to set tkinter textvariable running on separate Thread?如何设置 tkinter textvariable 在单独的线程上运行?
【发布时间】:2021-12-12 22:31:37
【问题描述】:

尝试更新在具有“main”函数变量的线程上运行的 tkinter“textvariable”。我实现了一个基于线程的解决方案,因此 tkinter 主循环之后的代码可以运行:https://stackoverflow.com/a/1835036/15409926

请分享我如何解决此错误。如果这不是更新“textvariable”的最简单方法,请分享替代方法。

代码:

from tkinter import *
from threading import Thread

class GUI(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.start()

    def run(self):
        self.root = Tk()
        self.var = StringVar()
        self.var.set("Initiated")

        screen_width = self.root.winfo_screenwidth()
        screen_height = self.root.winfo_screenheight()

        width = screen_width*.12
        height = screen_height
        x = screen_width - width
        y = screen_height*.025

        self.root.geometry('%dx%d+%d+%d' % (width, height, x, y))

        label = Label(self.root, textvariable= self.var)
        label.pack()

        self.root.mainloop()

gui = GUI()

def main():
    for i in range(1000):
        gui.var.set(f'Current Iteration: {i}')

if __name__ == '__main__':
    main()

窗口不更新: Tkinter window displays initial 'textvariable'

错误:

Traceback (most recent call last):
  File "test.py", line 36, in <module>
    main()
  File "test.py", line 33, in main
    gui.var.set(f'Current Iteration: {i}')
AttributeError: 'GUI' object has no attribute 'var'

【问题讨论】:

  • 首先,不要在单独的线程中运行tkinter 方法。您可以不在主线程中运行所有tkinter 的东西,但不要将其方法分离到不同的线程,这可能会破坏它。第二直接关于你的问题。在开始循环之前和循环中(否则太快)在main函数中放入一些time.sleep,因为线程启动需要一些时间
  • 在您的示例中,您可以使用root.after(millisecond, function) insread of threading - 但如果您有需要更多时间的功能,那么您应该使用queue 向线程发送新值,它应该使用@ 987654332@检查queue中是否有新值并直接在线程中更新值。
  • @furas 真正的函数是长时间运行的,所以使用 after 会一直暂停函数,对吧?
  • 如果你运行长时间运行的代码,那么它应该在单独的线程中运行,它应该使用queue 将值发送到带有 GUI 的线程,并且这个线程应该使用 after 定期检查是否有新的queue 中的信息并更新 GUI 中的值 - 因此它将在与 GUI 相同的线程中更新它。

标签: python multithreading tkinter


【解决方案1】:

大多数 GUI 不喜欢在单独的线程中更改小部件中的值。

您应该使用queue 向线程发送值,它应该使用root.after(time, function) 定期运行函数,该函数将从队列中获取值并在GUI 中更新值。

import tkinter as tk  # PEP8: `import *` is not preferred
from threading import Thread
import queue
import time  # simulate show program

class GUI(Thread):
    
    def __init__(self, queue):
        super().__init__()
        self.queue = queue
        self.start()

    def run(self):
        self.root = tk.Tk()
        self.var = tk.StringVar()
        self.var.set("Initiated")

        screen_width = self.root.winfo_screenwidth()
        screen_height = self.root.winfo_screenheight()

        width = int(screen_width*.12)
        height = int(screen_height)
        x = int(screen_width - width)
        y = int(screen_height*.025)

        self.root.geometry(f'{width}x{height}+{x}+{y}')

        label = tk.Label(self.root, textvariable=self.var)
        label.pack()
 
        # run first time after 100ms (0.1 second)
        self.root.after(100, self.check_queue)

        self.root.mainloop()

    def check_queue(self):
        #if not self.queue.empty():
        while not self.queue.empty():
            i = self.queue.get()
            self.var.set(f'Current Iteration: {i}')

        # run again after 100ms (0.1 second)
        self.root.after(100, self.check_queue)
        
def main():
    q = queue.Queue()
    gui = GUI(q)

    for i in range(1000):
        q.put(i)
        # simulate show program
        time.sleep(0.5)


if __name__ == '__main__':
    main()

PEP 8 -- Style Guide for Python Code

【讨论】:

  • widthheightxy 应转换为 int,否则它们似乎会引发错误。同样在这种特殊情况下,实际的for 循环在几毫秒内完成,而读取队列(尤其是每 100 毫秒)太慢而无法跟上,因此实际循环将完成但计数器将运行几秒钟
  • @Matiiss 你是对的整数值。至于100ms,你可以试试10ms,这只是一个例子——真正的程序可能运行得更慢。您可以使用while not self.queue.empty() 而不是if not self.queue.empty(): 来快速跳过队列中的某些值。
  • @Matiiss - 我为 windows 几何添加了int()while not self.queue.empty()
  • 太棒了,顺便说一句,我实际上从来没有想过在这样的循环中使用.empty 来快速通过队列,所以这是我学到的东西,不幸的是我不能投票两次 :)
  • 我没有看到我的 GUI 正在使用 for 循环中的 i 值进行更新。你能确认一下吗?
猜你喜欢
  • 2012-05-20
  • 2014-06-26
  • 2011-12-24
  • 2018-07-24
  • 2018-09-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-22
相关资源
最近更新 更多