我正在创建一个简单的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的工作原理有根本的误解。尽管如此,如果有人能指出正确的方向或弄清我的错误,我将不胜感激。
答案 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))