163

How do I handle the window close event (user clicking the 'X' button) in a Python Tkinter program?

4

9 回答 9

235

Tkinter supports a mechanism called protocol handlers. Here, the term protocol refers to the interaction between the application and the window manager. The most commonly used protocol is called WM_DELETE_WINDOW, and is used to define what happens when the user explicitly closes a window using the window manager.

You can use the protocol method to install a handler for this protocol (the widget must be a Tk or Toplevel widget):

Here you have a concrete example:

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()
于 2008-09-21T14:51:11.373 回答
41

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

这是一个工作示例,在 Windows 7 和 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按钮。

于 2013-02-11T19:46:39.517 回答
17

取决于 Tkinter 活动,特别是在使用 Tkinter.after 时,停止这个活动destroy()——即使使用协议()、按钮等——会干扰这个活动(“执行时”错误),而不是仅仅终止它. 几乎在所有情况下,最好的解决方案是使用标志。这是一个如何使用它的简单而愚蠢的示例(尽管我确信你们中的大多数人都不需要它!:)

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在正确的地方进行检查。

于 2018-04-12T17:50:02.133 回答
7

如果您想更改 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()

这使窗口再次可见

于 2020-12-06T19:03:43.497 回答
5

我要感谢 Apostolos 的回答让我注意到了这一点。这是 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并根据用户的回答设置标志。

于 2019-10-20T00:10:36.167 回答
1

最简单的代码是:

from tkinter import *
window = Tk()

隐藏窗口:window.withdraw()

出现窗口:window.deiconify()

从窗口退出:exit()

从窗口退出(如果您已制作 .exe 文件):

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

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

于 2021-09-02T12:19:08.123 回答
1

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

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

解释:

root.quit() 上面这行只是绕过了root.mainloop()ie ,如果执行了命令,ieroot.mainloop()仍然会在后台运行。quit()

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
于 2021-01-14T03:38:07.923 回答
0

尝试简单版本:

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-06-24T04:59:16.400 回答
-1

我说更简单的方法是使用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()
于 2020-11-18T10:05:46.680 回答