使用tkinter文本小部件创建日志框

3
我想创建一个框,告知用户该应用程序实际正在执行的操作。我创建了一个文本小部件,以显示我在应用程序的关键点编写的打印语句,以便它可以作为日志框。
为此,我将stdout重定向到小部件本身的子类“升级”,使用了此处另一篇帖子中的write方法。这确实有效,但我注意到一个问题,使盒子几乎无用。
如果运行代码,您可以看到句子一次性出现。对我来说更令人困惑的是,“wait2”函数的句子不仅一起出现,而且甚至调用函数“wait1”的打印语句也在进程结束时显示。
为什么会发生这种行为?我该怎么做才能看到按照执行顺序在框中显示的语句?
    from Tkinter import *
    import sys
    import time

    root = Tk()

    class addwritemethod(object): 
        def __init__(self, widget):
            self.widget = widget

        def write(self, string):
            self.widget.configure(state="normal")
            self.widget.insert("end", string) 
            self.widget.see("end") 
            self.widget.configure(state="disabled")

    def  wait1():
        print "Can you see me?"
        wait2()

    def  wait2():
        for i in range(10):
            time.sleep(5)
            print "Long time no see!"


    t_go = Button(root, text= "Start!", width =12, command = wait1) 
    t_go.pack(side = LEFT, padx = 20, pady = 20)


    tlog = Text(root,wrap = "word")
    tlog.pack(side="top", fill="both", expand=True)
    tlog.configure(state="disabled")

    sys.stdout = addwritemethod(tlog)

    mainloop()

编辑:感谢回答我的人,并向他们道歉:我没有提供所有必需的信息。 我在测试代码中使用time.sleep()只是为了展示行为。在真实的应用程序中,我使用Paramiko通过ssh传输文件,不使用sleep()。 也许我选择了错误的例子,但结果是一样的,所有的打印语句同时显示。


time.sleep() 与许多东西(包括 tkinter)不兼容。在 tkinter 中使用延迟,请使用 after() - TigerhawkT3
对不起,我没有正确解释这只是一个测试,以展示打印语句一次性打印的效果。在真实的应用程序中,我不使用sleep(),而是使用Paramiko。 - Lornioiz
2个回答

4
您还可以使用内置的日志记录模块来实现您的目标,即创建一个框,让用户了解应用程序实际正在执行的操作...一个日志框。
我有同样的需求,并收敛于提供的建议这里这里
下面是我创建的示例,用于说明使用Tkinter记录到GUI控件的概念。下面的示例按您的要求将日志记录到文本控件中,但您可以通过替换/复制类MyHandlerText来向其他GUI组件发送日志消息,例如MyHandlerLabelMyHandlerListbox等处理程序类(为处理程序类选择自己的名称)。然后,您就会拥有各种感兴趣的GUI控件的处理程序。对我来说最大的“顿悟”时刻是python.org鼓励的模块级别的getLogger概念。
import Tkinter
import logging
import datetime

# this item "module_logger" is visible only in this module,
# (but you can create references to the same logger object from other modules 
# by calling getLogger with an argument equal to the name of this module)
# this way, you can share or isolate loggers as desired across modules and across threads
# ...so it is module-level logging and it takes the name of this module (by using __name__)
# recommended per https://docs.python.org/2/library/logging.html
module_logger = logging.getLogger(__name__)

class simpleapp_tk(Tkinter.Tk):
    def __init__(self,parent):
        Tkinter.Tk.__init__(self,parent)
        self.parent = parent

        self.grid()

        self.mybutton = Tkinter.Button(self, text="ClickMe")
        self.mybutton.grid(column=0,row=0,sticky='EW')
        self.mybutton.bind("<ButtonRelease-1>", self.button_callback)

        self.mytext = Tkinter.Text(self, state="disabled")
        self.mytext.grid(column=0, row=1)

    def button_callback(self, event):
        now = datetime.datetime.now()
        module_logger.info(now)

class MyHandlerText(logging.StreamHandler):
    def __init__(self, textctrl):
        logging.StreamHandler.__init__(self) # initialize parent
        self.textctrl = textctrl

    def emit(self, record):
        msg = self.format(record)
        self.textctrl.config(state="normal")
        self.textctrl.insert("end", msg + "\n")
        self.flush()
        self.textctrl.config(state="disabled")

if __name__ == "__main__":

    # create Tk object instance
    app = simpleapp_tk(None)
    app.title('my application')

    # setup logging handlers using the Tk instance created above
    # the pattern below can be used in other threads...
    # ...to allow other thread to send msgs to the gui
    # in this example, we set up two handlers just for demonstration (you could add a fileHandler, etc)
    stderrHandler = logging.StreamHandler()  # no arguments => stderr
    module_logger.addHandler(stderrHandler)
    guiHandler = MyHandlerText(app.mytext)
    module_logger.addHandler(guiHandler)
    module_logger.setLevel(logging.INFO)
    module_logger.info("from main")    

    # start Tk
    app.mainloop()

1
当你调用sleep时,应用程序会准确地执行该操作:它休眠。当它正在休眠时,它无法更新显示。作为一般规则,在GUI中永远不应该调用sleep
话虽如此,一个快速的解决方法是确保在打印日志后调用update,以便Tkinter有机会更新屏幕。在write的结尾添加self.widget.update_idletasks()(重新绘制屏幕被认为是“空闲任务”)。
这不是一个适当的修复方法,但足以说明为什么数据不会出现。一个适当的修复方法涉及到调用sleep。关于这方面有许多与之相关的stackoverflow示例,其中几乎所有的都涉及使用after方法。

感谢您的回答。我编辑了原始帖子以指出我在实际代码中没有使用sleep()。然而,为什么一旦睡眠时间结束(应用程序“唤醒”),它就不会写入任何一行呢?我期望每5秒钟会出现一行,但是它们都一次性显示出来了。 - Lornioiz
@Lornioiz:当你调用update时,所有挂起的重绘事件都将被处理。如果你写了几个项目而没有调用update,它们将同时出现。如果你调用了一个长时间运行的命令,在该命令运行时将不会发生任何事情。 - Bryan Oakley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接