如何处理 Tkinter 的关窗事件?

如何处理 Python Tkinter 程序中的窗口关闭事件(用户单击“ X”按钮) ?

234737 次浏览

Tkinter 支持一种称为 协议处理程序的机制。在这里,术语 规定指的是应用程序和窗口管理器之间的交互。最常用的协议称为 WM_DELETE_WINDOW,用于定义当用户使用窗口管理器显式关闭窗口时发生的情况。

对于这个协议,可以使用 protocol方法到 安装处理程序(小部件必须是 TkToplevel小部件) :

这里有一个具体的例子:

import tkinter as tk
from tkinter import messagebox


root = tk.Tk()


def on_closing():
if messagebox.askokcancel("Quit", "Do you want to quit?"):
root.destroy()


root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()

Matt 展示了关闭按钮的一个经典修改。
另一种是让关闭按钮最小化窗口。
可以通过使用 < em > iconify 方法重现此行为
协议方法的第二个参数。

下面是一个在 Windows7和10上测试的工作示例:

# Python 3
import tkinter
import tkinter.scrolledtext as scrolledtext


root = tkinter.Tk()
# make the top right close button minimize (iconify) the main window
root.protocol("WM_DELETE_WINDOW", root.iconify)
# make Esc exit the program
root.bind('<Escape>', lambda e: root.destroy())


# create a menu bar with an Exit command
menubar = tkinter.Menu(root)
filemenu = tkinter.Menu(menubar, tearoff=0)
filemenu.add_command(label="Exit", command=root.destroy)
menubar.add_cascade(label="File", menu=filemenu)
root.config(menu=menubar)


# create a Text widget with a Scrollbar attached
txt = scrolledtext.ScrolledText(root, undo=True)
txt['font'] = ('consolas', '12')
txt.pack(expand=True, fill='both')


root.mainloop()

在本例中,我们为用户提供了两个新的退出选项:
经典的文件→退出,还有 Esc按钮。

根据 Tkinter 活动,特别是在使用 Tkinter.after 时,使用 destroy()停止这个活动——即使使用 protocol ()、按钮等——也会干扰这个活动(“ while running”error) ,而不仅仅是终止它。几乎所有情况下的最佳解决方案都是使用标志。这里有一个简单而愚蠢的例子来说明如何使用它(尽管我确信你们中的大多数人并不需要它!)!:)

from Tkinter import *


def close_window():
global running
running = False  # turn off while loop
print( "Window closed")


root = Tk()
root.protocol("WM_DELETE_WINDOW", close_window)
cv = Canvas(root, width=200, height=200)
cv.pack()


running = True;
# This is an endless loop stopped only by setting 'running' to 'False'
while running:
for i in range(200):
if not running:
break
cv.create_oval(i, i, i+1, i+1)
root.update()

这很好地结束了图形活动。您只需要在正确的位置检查 running

试试简单的版本:

import tkinter


window = Tk()


closebutton = Button(window, text='X', command=window.destroy)
closebutton.pack()


window.mainloop()


或者如果您想添加更多命令:

import tkinter


window = Tk()




def close():
window.destroy()
#More Functions




closebutton = Button(window, text='X', command=close)
closebutton.pack()


window.mainloop()

我想感谢阿波斯托洛斯的回答让我注意到了这一点。下面是2019年 Python 3的一个更详细的示例,其中有更清晰的描述和示例代码。


请注意,destroy()(或根本没有自定义窗口关闭处理程序)会在用户关闭窗口 以及它所有的运行回调时立即破坏该窗口。

这可能对您不利,具体取决于您当前的 Tkinter 活动,尤其是在使用 tkinter.after(定期回调)时。您可能正在使用一个回调函数来处理一些数据并将其写入磁盘... ... 在这种情况下,您显然希望数据写入完成而不会被突然终止。

最好的解决方案是使用一个标志。因此,当用户请求关闭窗口时,您将其标记为一个标志,然后对其作出反应。

(注意: 我通常将 GUI 设计为精心封装的类和独立的工作线程,我肯定不会使用“全局”(我使用类实例变量代替) ,但这是一个简单的例子,用来演示当用户关闭窗口时 Tk 如何突然杀死定期回调...)

from tkinter import *
import time


# Try setting this to False and look at the printed numbers (1 to 10)
# during the work-loop, if you close the window while the periodic_call
# worker is busy working (printing). It will abruptly end the numbers,
# and kill the periodic callback! That's why you should design most
# applications with a safe closing callback as described in this demo.
safe_closing = True


# ---------


busy_processing = False
close_requested = False


def close_window():
global close_requested
close_requested = True
print("User requested close at:", time.time(), "Was busy processing:", busy_processing)


root = Tk()
if safe_closing:
root.protocol("WM_DELETE_WINDOW", close_window)
lbl = Label(root)
lbl.pack()


def periodic_call():
global busy_processing


if not close_requested:
busy_processing = True
for i in range(10):
print((i+1), "of 10")
time.sleep(0.2)
lbl["text"] = str(time.time()) # Will error if force-closed.
root.update() # Force redrawing since we change label multiple times in a row.
busy_processing = False
root.after(500, periodic_call)
else:
print("Destroying GUI at:", time.time())
try: # "destroy()" can throw, so you should wrap it like this.
root.destroy()
except:
# NOTE: In most code, you'll wanna force a close here via
# "exit" if the window failed to destroy. Just ensure that
# you have no code after your `mainloop()` call (at the
# bottom of this file), since the exit call will cause the
# process to terminate immediately without running any more
# code. Of course, you should NEVER have code after your
# `mainloop()` call in well-designed code anyway...
# exit(0)
pass


root.after_idle(periodic_call)
root.mainloop()

这段代码将向您展示 WM_DELETE_WINDOW处理程序的运行情况,即使我们的自定义 periodic_call()在工作/循环中间忙碌也是如此!

我们使用一些非常夸张的 .after()值: 500毫秒。这只是为了让你很容易看到关闭与否之间的区别,而周期性调用是繁忙的,或不... 如果你关闭,而数字更新,你会看到 WM_DELETE_WINDOW发生 同时你的周期性调用“是繁忙的处理: 真”。如果在暂停数字时关闭(这意味着此时不处理周期性回调) ,则可以看到关闭是在“不忙”时发生的。

在实际使用中,您的 .after()将使用大约30-100毫秒的时间来获得一个响应的 GUI。这只是一个演示,帮助您了解如何保护自己免受 Tk 的默认“关闭时立即中断所有工作”行为的影响。

总结: 让 WM_DELETE_WINDOW处理程序设置一个标志,然后周期性地检查该标志,并在安全时手动 .destroy()窗口(当您的应用程序完成所有工作时)。

PS: 如果用户真的想关闭窗口,你也可以使用 WM_DELETE_WINDOW问吧; 如果他们回答“不”,你就不要设置标志。很简单。您只需在 WM_DELETE_WINDOW中显示一个消息框,并根据用户的回答设置标志。

我认为更简单的方法是使用 break命令,比如

import tkinter as tk
win=tk.Tk
def exit():
break
btn= tk.Button(win, text="press to exit", command=exit)
win.mainloop()

或使用 sys.exit()

import tkinter as tk
import sys
win=tk.Tk
def exit():
sys.exit
btn= tk.Button(win, text="press to exit", command=exit)
win.mainloop()

如果你想改变 x 按钮的功能或者让它无法关闭,试试这个。

yourwindow.protocol("WM_DELETE_WINDOW", whatever)

那就反抗“无所谓”的含义

def whatever():
# Replace this with your own event for example:
print("oi don't press that button")

你也可以这样做,当你关闭这个窗口,你可以像这样调用它回来

yourwindow.withdraw()

这将隐藏窗口,但不关闭它

yourwindow.deiconify()

这使得窗口再次可见

您应该使用 delete ()关闭 tkinter 窗口。

   from Tkinter import *
root = Tk()
Button(root, text="Quit", command=root.destroy).pack()
root.mainloop()

说明:

root.quit() 如果执行 quit()命令,上面的代码行只是绕过了 root.mainloop(),即 root.mainloop()仍然在后台运行。

root.destroy()destroy()命令从 root.mainloop()中消失时,即 root.mainloop()停止。

所以你只是想退出程序,所以你应该使用 root.destroy(),因为它会停止 mainloop ()’。

但是,如果你想运行一些无限循环,你不想破坏你的 Tk 窗口,并希望执行一些代码后,root.mainloop()行,那么你应该使用 root.quit()。 例如:

from Tkinter import *
def quit():
global root
root.quit()


root = Tk()
while True:
Button(root, text="Quit", command=quit).pack()
root.mainloop()
#do something

最简单的代码是:

from tkinter import *
window = Tk()

隐藏窗口: window.withdraw()

显示窗口: window.deiconify()

从窗口退出: exit()

用于从窗口退出(如果您创建了. exe 文件) :

from tkinter import *
import sys
window = Tk()
sys.exit()

当然,你必须放置一个按钮,并在函数中使用上面的代码,这样你就可以在按钮的命令部分中键入函数的名称

你可使用:

root = Tk()
def func():
print('not clossed')
root.protocol('wm_delete_window', func)
root.mainloop()
def on_closing():
if messagebox.askokcancel("Quit", "would you like to quit"):
window.destroy()




window.protocol("WM_DELETE_WINDOW", on_closing)

你可以像这样处理一个窗口关闭事件,如果你想做一些其他的事情,只需要改变在 on _ close ()函数中发生的事情。