实时更新Tkinter文本组件而非在类结束后

5 投票
3 回答
11507 浏览
提问于 2025-04-16 20:53

我现在有点麻烦,因为我在一个保密的机器上写这个,无法复制粘贴到这里。作为一个新手,我的做法可能有点不寻常。

我用Tkinter写了一个图形界面,里面有几个按钮。每个按钮都连接到一个类,这个类实际上运行一个简短的脚本。当点击按钮时,我会初始化一个叫做 log_window 的类,它其实就是一个Tkinter的文本小部件。接着,我创建一个全局变量,把 log 连接到我刚创建的 log_window,在脚本运行时,我把 sys.stdout/stderr 重定向到 log(我为此专门创建了一个写入方法)。一切看起来都没问题,但 log_window 的文本小部件在我重定向的输出更新之前,直到调用它的类完成后才会更新。不过,如果我在类里面直接使用 print,它会按调用的顺序打印出来。

举个例子

import Tkinter
from Tkinter import *
import time

class log_window:
    def __init__(self,master):
        self.textframe = Tkinter.Frame(master)
        self.text = Text(self.textframe)
        self.text.pack()
        self.textframe.pack()
    def write(self,text):
        self.text.insert(END,text)

class some_func1: # This effectively waits 5 seconds then prints both lines at once
    def __init__(self,master):
        log.write("some text")
        time.sleep(5)
        log.write("some text")

class some_func2: # This prints the first object, waits 5 seconds, then prints the second
    def __init__(self,master):
        print "some text"
        time.sleep(5)
        print "some text"

if __name__ == '__main__':
    global log    
    root = Tk()
    log = log_window(root)
    root.after(100,some_func1, root)
    root.after(100,some_func2, root)
    root.mainloop()

抱歉我的例子可能有点不清楚,但我觉得它表达了我的意思。我做的重定向是通过 Popen 和一些系统调用,但这些不是问题的关键,所以我只强调了我认为是问题的核心部分。

3 个回答

1

你需要在 self.text.insert(END,text) 之后加上 self.text.update() 来更新你的小部件内容。

2

当你调用 sleep 的时候,你的整个图形界面(GUI)会卡住。你要记住,图形界面是通过一个事件循环来运行的,这个循环是一个无限循环,包裹着你所有的代码。事件循环负责在你改变界面元素时重新绘制它们。当有事件发生时,它会在这个循环中调用你的代码,所以只要你的代码在运行,事件循环就无法继续循环。

你有几种选择。一个方法是在向界面元素添加文本后调用 update_idletasks。这样可以让事件循环处理“空闲”事件——也就是在程序没有其他事情做的时候安排执行的任务。重新绘制屏幕就是其中一个事件,还有其他的事件。

另一个选择是把你的函数放在一个线程或者单独的进程中运行。因为 Tkinter 不是线程安全的,所以这些线程或进程不能直接和图形界面沟通。它们需要把消息放到一个队列里,然后你的主线程(图形界面线程)必须去检查这个队列,取出消息。把这个代码集成到你的日志类里是很简单的,使用事件循环可以轻松地检查队列——只需要写一个方法来从队列中取出消息并插入到界面元素中,然后用 after 方法在几百毫秒后再次调用自己。

9

我对Tkinter的并发处理不是很了解,但通过尝试发现,如果在每次调用log.write之后加上

master.update_idletasks()

,它就会按时更新。你可以给log加一个.flush()的方法来实现这个功能(就像文件处理一样),或者你也可以让log.write在写入后自己调用这个方法。

撰写回答