Tkinter
window close event
Python GUI
event handling
Tkinter tutorial

How do I handle the window close event in Tkinter?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Tkinter, closing a window is not just about destroying the GUI. It is often the moment to confirm exit, save unsaved data, stop background tasks, or clean up resources. Tkinter exposes the window manager close action through the WM_DELETE_WINDOW protocol, and that is the correct place to intercept the user clicking the title-bar close button.

Registering a Close Handler

The main window object, usually called root, lets you replace the default close behavior with your own callback.

python
1import tkinter as tk
2
3
4def on_close():
5    print("Window is closing")
6    root.destroy()
7
8
9root = tk.Tk()
10root.title("Example")
11root.protocol("WM_DELETE_WINDOW", on_close)
12root.mainloop()

The important line is:

python
root.protocol("WM_DELETE_WINDOW", on_close)

When the user clicks the close button, Tkinter calls on_close instead of destroying the window immediately.

Asking for Confirmation Before Exit

A common use case is confirming that the user really wants to quit.

python
1import tkinter as tk
2from tkinter import messagebox
3
4
5def on_close():
6    should_quit = messagebox.askyesno(
7        title="Quit",
8        message="Do you want to close the application?"
9    )
10    if should_quit:
11        root.destroy()
12
13
14root = tk.Tk()
15root.title("Editor")
16root.protocol("WM_DELETE_WINDOW", on_close)
17root.mainloop()

This is especially helpful when closing the window could discard unsaved work.

Cleaning Up Resources

The close handler is also the right place to stop background activity. For example, if your app has an open file, socket, or scheduled callback, close or cancel it before destroying the root window.

python
1import tkinter as tk
2
3
4def tick():
5    print("running")
6    app_state["job"] = root.after(1000, tick)
7
8
9def on_close():
10    job = app_state.get("job")
11    if job is not None:
12        root.after_cancel(job)
13    root.destroy()
14
15
16root = tk.Tk()
17app_state = {"job": None}
18app_state["job"] = root.after(1000, tick)
19root.protocol("WM_DELETE_WINDOW", on_close)
20root.mainloop()

Without that cleanup, callbacks may continue trying to run during shutdown and produce hard-to-debug errors.

Handling Child Windows

If you create Toplevel windows, each one can have its own close protocol. That means a child window can confirm or clean up independently of the main application window.

python
1import tkinter as tk
2
3
4def close_child():
5    print("Closing child window")
6    child.destroy()
7
8
9root = tk.Tk()
10child = tk.Toplevel(root)
11child.title("Child")
12child.protocol("WM_DELETE_WINDOW", close_child)
13root.mainloop()

This is useful when different windows manage different resources.

Saving State Before Destruction

Many desktop apps save size, position, or form data when closing. The close callback is a convenient place to do that.

python
1import json
2import tkinter as tk
3
4
5def on_close():
6    state = {
7        "width": root.winfo_width(),
8        "height": root.winfo_height(),
9        "title": root.title(),
10    }
11    with open("window_state.json", "w", encoding="utf-8") as file:
12        json.dump(state, file)
13    root.destroy()
14
15
16root = tk.Tk()
17root.title("Persistent Window")
18root.protocol("WM_DELETE_WINDOW", on_close)
19root.mainloop()

That pattern keeps shutdown behavior explicit instead of scattered through unrelated parts of the program.

quit() vs destroy()

Tkinter developers sometimes confuse quit() and destroy(). quit() stops the Tkinter main loop, while destroy() actually removes the widgets and closes the window.

For most close handlers, destroy() is the right final step. If you only call quit() without destroying the window, widgets may still exist and cleanup may be incomplete.

Common Pitfalls

A common mistake is forgetting to call destroy() at the end of the close handler. The callback runs, but the window stays open.

Another issue is doing long-running work directly in the close callback. If saving or cleanup takes noticeable time, the app can look frozen during shutdown.

Developers also sometimes attach cleanup logic to unrelated buttons and assume it covers the title-bar close button too. It does not. Use WM_DELETE_WINDOW explicitly.

Finally, remember that each Toplevel window can have its own close behavior. Do not assume the root window handler automatically manages every child window correctly.

Summary

  • Use root.protocol("WM_DELETE_WINDOW", callback) to handle the close button.
  • Put confirmation, cleanup, and save logic inside that callback.
  • Finish by calling destroy() so the window actually closes.
  • Register separate handlers for Toplevel windows when needed.
  • Treat shutdown as a real event with explicit resource cleanup, not just a UI action.

Course illustration
Course illustration

All Rights Reserved.