如何在程序上下文中正确实现tkinter的.after()方法?

时间:2019-04-14 17:57:38

标签: python oop tkinter

我正在创建一个简单的GUI程序,该程序使用Python和Tkinter记录用户按下界面上的按钮时(通过将信息附加到.txt文件中)来记录时间/日期,并将电子邮件发送到通知收件人日志已更新的地址列表。

该程序具有三个主要框架/屏幕,我希望用户可以浏览。屏幕之间的导航应基于时间。换句话说,我希望在按下Tkinter按钮(我已经使用Tkinter小部件的'command'参数建立的)后将用户从主屏幕重定向到辅助屏幕,然后自动将其重定向延迟5秒后返回主屏幕。

我了解在GUI程序中不鼓励使用time.sleep()。但是,在实现Tkinter的.after()方法时遇到了一些麻烦,并且还没有完全能够达到我想要的结果。

我已附上我的程序代码的简化示例,以对我的问题进行建模:

import tkinter as tk


class mainApplication(tk.Tk):

    def __init__(self, *args, **kwargs):

        tk.Tk.__init__(self, *args, **kwargs)

        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)
        self.frames = {}

        for F in (MainScreen, AcknowledgeScreen):

            frame = F(container, self)
            self.frames[F] = frame
            frame.grid(row=0, column=0, sticky="nsew")

        self.show_frame(MainScreen)

    def show_frame(self, cont):

        frame = self.frames[cont]
        frame.tkraise()


class MainScreen(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        label = tk.Label(self, text="Press Button to Log Time")
        label.pack()

        button = tk.Button(self, text="Confirm", command=lambda: controller.show_frame(AcknowledgeScreen))
        button.pack()


class AcknowledgeScreen(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        label = tk.Label(self, text="Logging Completed. Please Wait.")
        label.pack()

        # The implementation below is giving me trouble.

        self.after(5000, controller.show_frame(MainScreen))


root = mainApplication()
root.mainloop()

我尝试过的其他解决方案(针对感兴趣的领域)包括:

# Attempt 1
self.after(5000, controller.show_frame(MainScreen))  # This code waits 5 seconds before opening the GUI window; does not redirect back to MainScreen from AcknowledgeScreen.

# Attempt 2
root.after(5000, controller.show_frame(MainScreen))  # This code throws an error 'NameError: name 'root' is not defined.

# Attempt 3
label.after(5000, controller.show_frame(MainScreen))  # This code waits 5 seconds before opening the GUI window; does not redirect back to MainScreen from AcknowledgeScreen.

不幸的是,在开始使用Tkinter之前,我从未接触过面向对象的编程,因此,我认为我的错误可能是由于对OOP的工作原理有根本的误解。尽管如此,如果有人能指出正确的方向或弄清我的错误,我将不胜感激。

3 个答案:

答案 0 :(得分:1)

mainApplication中,两个屏幕都被初始化,并且它们的类用于字典键映射到它们的实例。

根据操作顺序,在显示MainScreen之后,应按堆叠顺序升高AcknowledgeScreen

该操作不应在AcknowledgeScreen.__init__中进行,除非您在需要时初始化屏幕。

您要将其移至MainScreen。您可以通过以下方式重构MainScreen

class MainScreen(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(self, text="Press Button to Log Time")
        label.pack()
        button = tk.Button(self, text="Confirm", command=self.confirm)
        button.pack()

    def confirm(self):
        self.controller.show_frame(AcknowledgeScreen)
        self.after(5000, self.back)

    def back(self):
        self.controller.show_frame(MainScreen)

答案 1 :(得分:1)

让我们看一下这段代码:

self.after(5000, controller.show_frame(MainScreen))  

上面的代码与此完全相同:

result = controller.show_frame(MainScreen)
self.after(5000, result)

注意会发生什么?函数controller.show_frame(MainScreen)立即执行,而不是由after执行。

相反,您需要给after一个可调用的 -概括地说,是对函数的引用。如果该函数需要其他参数,则可以在调用after时添加这些参数:

self.after(5000, controller.show_frame, MainScreen)

上面的代码告诉after在五秒钟内运行controller.show_frame,并在调用时将参数MainScreen传递给它。

答案 2 :(得分:0)

after()(例如bind()command=)需要callback-这意味着没有()和参数的函数名。

使用lambda

  self.after(5000, lambda:controller.show_frame(MainScreen))

但是我看到了另外一个问题。

运行程序时,它将在mainApplication.__init__中创建所有帧的实例,因此它在启动时也运行AcknowledgeScreen.__init__-并在启动时运行after()。它不会等待您的点击。

顺便说一句:因为框架是在mainApplication.__init__内部创建的,所以它们不能使用root将在mainApplication.__init__结束其工作之后创建的框架。

您必须在方法中添加一些部分,并且每次单击按钮时都必须运行。

class MainScreen(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller

        label = tk.Label(self, text="Press Button to Log Time")
        label.pack()

        button = tk.Button(self, text="Confirm", command=self.change)
        button.pack()

    def change(self):
        self.controller.show_frame(AcknowledgeScreen)
        self.controller.frames[AcknowledgeScreen].change_after()      

class AcknowledgeScreen(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)
        self.controller = controller

        label = tk.Label(self, text="Logging Completed. Please Wait.")
        label.pack()

    def change_after(self):
        # The implementation below is giving me trouble.

        #both works
        #root.after(5000, lambda:self.controller.show_frame(MainScreen))
        self.after(5000, lambda:self.controller.show_frame(MainScreen))