如何处理Tkinter中的窗口关闭事件?

时间:2008-09-21 14:46:20

标签: python events tkinter window

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

10 个答案:

答案 0 :(得分:128)

Tkinter支持名为protocol handlers的机制。这里,术语协议指的是应用程序和窗口管理器之间的交互。最常用的协议称为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()

答案 1 :(得分:21)

马特已经展示了关闭按钮的一个经典修改 另一种是使关闭按钮最小化窗口 您可以通过iconify方法来复制此行为 是protocol方法的第二个参数。

这是一个在Windows 7上测试的工作示例:

# Python 3
import tkinter
import tkinter.scrolledtext as scrolledtext

class GUI(object):

    def __init__(self):
        root = self.root = tkinter.Tk()
        root.title('Test')

    # 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')

gui = GUI()
gui.root.mainloop()

在此示例中,我们为用户提供了两个新的退出选项:
经典文件菜单 - &gt;退出,还有 Esc 按钮。

答案 2 :(得分:3)

根据Tkinter活动,结束esp。当使用Tkinter.after时,使用destroy()停止此活动 - 即使使用protocol(),按钮等 - 将干扰此活动(&#34;执行&#34;错误)而不仅仅是终止它。几乎在所有情况下,最好的解决方案是使用标志。这是一个如何使用它的简单,愚蠢的例子(虽然我确信大多数人都不需要它!)

from Tkinter import *

def close_window():
  global running
  running = False
  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即可。

答案 3 :(得分: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()

答案 4 :(得分:0)

我要感谢Apostolos的回答,这一问题引起了我的注意。这是2019年Python 3的更为详细的示例,带有更清晰的描述和示例代码。


请注意以下事实:destroy()(或完全没有自定义窗口关闭处理程序)将在用户关闭窗口及其所有正在运行的回调时立即销毁。 / h2>

这可能对您不利,具体取决于您当前的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发生了< 期间,您的定期通话“正在处理:True”。如果您在数字暂停时关闭(这意味着此时不处理定期回调),则会看到关闭发生在“不忙”期间。

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

总而言之:让WM_DELETE_WINDOW处理程序设置一个标志,然后在安全的情况下(当您的应用程序完成所有工作后)定期并手动.destroy()检查该标志。

PS:如果用户确实要关闭窗口,也可以使用WM_DELETE_WINDOW来向用户询问;如果他们回答“否”,则无需设置标志。非常简单您只需在WM_DELETE_WINDOW中显示一个消息框,然后根据用户的答案设置标志即可。

答案 5 :(得分:0)

如果您想更改x按钮的功能或将其设置为完全无法关闭它,请尝试执行此操作。

yourwindow.protocol("WM_DELETE_WINDOW", whatever)

然后反驳“什么”的含义

def whatever():
    #Replace this with what you want python to do when the user hits the x button

您也可以创建它,以便在关闭该窗口时可以像这样调用它

yourwindow.withdraw() 

这会隐藏窗口,但不会关闭

yourwindow.deiconify()

这将使窗口再次可见

答案 6 :(得分:0)

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

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

说明:

root.quit() 上面的行只是绕过 root.mainloop(),即 root.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

答案 7 :(得分:-1)

from tkinter import*
root=Tk()
exit_button=Button(root,text="X",command=root.quit)
root.mainloop()

答案 8 :(得分:-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()

答案 9 :(得分:-14)

使用closeEvent

def closeEvent(self, event):
# code to be executed