用 Tkinter 等待一定的时间

Posted

技术标签:

【中文标题】用 Tkinter 等待一定的时间【英文标题】:Waiting certain amount of time with Tkinter 【发布时间】:2019-01-16 18:51:09 【问题描述】:

我有一个 Tkinter 程序,我想暂停 3 秒。 time.sleep 不起作用,after 方法并不能完全按照我的意愿行事。

这是一个示例代码:

from Tkinter import *
def waithere():
    print "waiting..."
root = Tk()

print "1"
root.after(3000,waithere)
print "2"

root.mainloop()

输出:

1
2
*3 seconds*
waiting...

我想要的输出:

1
waiting...
*3 seconds*
2

谢谢。

【问题讨论】:

暂停有什么意义?这真的是您需要做的,还是您只是认为暂停与您真正想做的事情相似?换句话说,您真的希望 GUI 暂停,还是只想在三秒钟内发生一些事情?您的小示例程序根本不是 tkinter 设计的工作方式。 @BryanOakley 我希望程序在等待 3 秒后执行一些操作。 你没有真正回答这个问题。为什么需要等待?您已经知道如何使用after,那么为什么不将after 也用于您希望将来发生的其他事情呢? 【参考方案1】:

通常让 GUI 等待某些东西是一个非常糟糕的主意。这并不意味着基于事件的程序是如何工作的。或者更准确地说,GUI 已经处于永久等待状态,而您不想用自己的等待来阻止它。

话虽如此,tkinter 有办法等到某些事情发生。例如,您可以使用“等待”函数之一,例如wait_variable、wait_window 或wait_visibility。

假设您希望waithere 进行等待,您可以使用wait_variable 进行等待,并在给定时间后使用after 设置变量。

这是基于您的原始代码的解决方案:

from Tkinter import *
def waithere():
    var = IntVar()
    root.after(3000, var.set, 1)
    print("waiting...")
    root.wait_variable(var)

root = Tk()

print "1"
waithere()
print "2"

root.mainloop()

使用这些方法的好处是您的代码在等待时仍然能够响应事件。

【讨论】:

【参考方案2】:

仅供参考,请勿在Tkinter 中使用长循环或无限循环;它们将阻止 UI 响应用户事件(AKA 冻结)。我被教导的方法是使用after() 函数定期更新字段。

after() 函数创建一个警报回调,这意味着在调用时(使用正确的参数)它将对目标方法的调用进行排队(在下面的示例中 def update(self) 与我们输入的延迟。您可以在退出循环的类。在__init__ 上创建,然后当设置为False 时不再调用after()

这是一个创建继承 Tkinter.Frame 的类以继承功能的示例。

try:
    import tkinter as tk
except:
    import Tkinter as tk

import datetime


class DelayedUpdateUI(tk.Frame):
    def __init__(self, master=None, **kw):
        # Create widgets, if any.
        tk.Frame.__init__(self, master=master, **kw)
        self.timeStr = tk.StringVar()
        self.lblTime = tk.Label(self, textvariable=self.timeStr)
        self.lblTime.grid()
        # Call update to begin our recursive loop.
        self.update()

    def update(self):
        self.timeStr.set(datetime.datetime.now())
        # We use after( milliseconds, method_target ) to call our update
        # method again after our entered delay. :)
        self.after(1000, self.update)


if __name__ == '__main__':
    root = tk.Tk()
    DelayedUpdateUI(root).grid()
    root.mainloop()

【讨论】:

这并不能回答我的问题 它不会直接回答您的问题,但会为您提供所需的信息,让您可以做自己想做的事。有什么令人困惑的吗?我非常愿意回答问题。 是的,这很令人困惑。如果你能告诉我如何制作一个程序以这种方式输出1 waiting... *3 seconds* 2,我会很高兴。 小心你的术语。 tkinter 对 after 所做的不是任何意义上的“线程”。 Tkinter 是单线程的,即使使用 after【参考方案3】:

基于Bryan's answer的建议:

我从基于事件的角度理解推荐的方式,但对我来说感觉不是很直观。每次需要时,我都必须查找技巧。因此,我创建了一个小的 mixin 类,使使用更加直观:

import tkinter as tk


class TkWaitMixin:
    """Simple wait timer that makes Tk waiting functionality 
    more intiutive. Applies the recommended way according to
    https://***.com/a/51770561/12646289.
    """
    def start_wait_timer(self, milliseconds):
        self.resume = tk.BooleanVar(value=False)
        self.master.after(milliseconds, self.resume.set, True)
        # Assume master attribute is available: 
        # https://***.com/a/53595036/12646289
        
    def wait_on_timer(self):
        self.master.wait_variable(self.resume)

示例用法:

import tkinter as tk

class MyWindow(tk.Tk, TkWaitMixin):
    def __init__(self, master):
        self.master = master
        self.message_label = tk.Label('')
        self.message_label.pack(padx=50, pady=50)
        
    def show_message(self, message, milliseconds):
        self.start_wait_timer(milliseconds)
        self.message_label['text'] = message
        self.wait_on_timer()
        self.message_label['text'] = ''
        
root = tk.Tk()
mywin = MyWindow(master=root)
mywin.show_message('Hello world', 2000)
root.mainloop()

显然,这仅在您在 tkinter 代码中使用类时才有用。另请注意,master 属性应该在添加了 mixin 的主类中可用。

编辑


或者,使用上下文管理器可以使使用变得更加容易:

import tkinter as tk

class TkWait:
    def __init__(self, master, milliseconds):
        self.duration = milliseconds
        self.master = master
        
    def __enter__(self):
        self.resume = tk.BooleanVar(value=False)
        return self
        
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.master.after(self.duration, self.resume.set, True)
        self.master.wait_variable(self.resume)

请注意,退出上下文管理器时开始等待。

示例用法:

import tkinter as tk

class MyWindow(tk.Tk):
    def __init__(self, master):
        self.master = master
        self.message_label = tk.Label('')
        self.message_label.pack(padx=50, pady=50)

    def show_message(self, message, milliseconds):
        with TkWait(self.master, milliseconds):
            self.message_label['text'] = message
        self.message_label['text'] = ''

root = tk.Tk()
mywin = MyWindow(master=root)
mywin.show_message('Hello world', 2000)
root.mainloop()

【讨论】:

以上是关于用 Tkinter 等待一定的时间的主要内容,如果未能解决你的问题,请参考以下文章

tkinter + cefpython 仿美团桌面程序

使用 tkinter 的简单动画

求教 探讨python tkinter的messagebox

[Tkinter 教程15] event 事件绑定

TKinter 教程

tkinter 和 time.sleep