How do I handle the window close event in Tkinter?
Asked Answered
P

11

205

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

Puglia answered 21/9, 2008 at 14:46 Comment(0)
P
299

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()
Puglia answered 21/9, 2008 at 14:51 Comment(8)
If you are using something like Twisted that maintains an event loop independently or Tkinter (eg: twisted's reactor object) make sure the outer main loop is stopped with whatever smenatics it provides for that purpose (eg: reactor.stop() for twisted)Glower
On my Python 2.7 on Windows, Tkinter didn't have a submodule messagebox. I used import tkMessageBox as messageboxClinical
I think you should at make known that you copied this answer and code from someone/where else.Korn
I don't know, that's not the code that I originally posted.Puglia
Doesn't work for me. It doesn't change classic Python's chaotic reaction to the interruption of graphics when one hard-closes the window (e.g. with Alt+F4).Xerxes
@MitchMcMabers: on the other hand, there have been virtually no significant changes to tkinter in a very long time.Endamage
This throws error if the window is closed by window.destroy(). It says: can't invoke "wm" command: application has been destroyedWeksler
Setting the protocol this way only works for the window being closed by clicking on the close button the OS puts on the window (or an OS-specific keyboard command like alt-F4 is pressed, but not with many if not most of the other ways the tkinter mainloop() might be terminated (which also destroys the window).Brachial
T
45

Matt has shown one classic modification of the close button.
The other is to have the close button minimize the window.
You can reproduced this behavior by having the iconify method
be the protocol method's second argument.

Here's a working example, tested on 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()

In this example we give the user two new exit options:
the classic File → Exit, and also the Esc button.

Tomika answered 11/2, 2013 at 19:46 Comment(3)
Definitely interesting! However, I would immediately uninstall a program that didn't quit when I hit the close button.Defilade
Yea, it violates the principle of least astonishment. I'm going to leave it, since it's still a valid answer to the question, and the example has some bonus educational value.Tomika
Absolutely -- it got an upvote from me. :-)Defilade
T
19

If you want to change what the x button does or make it so that you cannot close it at all try this.

yourwindow.protocol("WM_DELETE_WINDOW", whatever)

then define what "whatever" means

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

You can also make it so that when you close that window you can call it back like this

yourwindow.withdraw() 

This hides the window but does not close it

yourwindow.deiconify()

This makes the window visible again

Threnody answered 6/12, 2020 at 19:3 Comment(1)
This is simple, but yourwindow.destroy() must be added to 'whatever', otherwise how to access 'yourwindow.deiconify()'? the program will hang and the only way to restart is close the IDE.Derman
X
16

Depending on the Tkinter activity, and especially when using Tkinter.after, stopping this activity with destroy() -- even by using protocol(), a button, etc. -- will disturb this activity ("while executing" error) rather than just terminate it. The best solution in almost every case is to use a flag. Here is a simple, silly example of how to use it (although I am certain that most of you don't need it! :)

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() 

This terminates graphics activity nicely. You only need to check running at the right place(s).

Xerxes answered 12/4, 2018 at 17:50 Comment(0)
C
8

I'd like to thank the answer by Apostolos for bringing this to my attention. Here's a much more detailed example for Python 3 in the year 2019, with a clearer description and example code.


Beware of the fact that destroy() (or not having a custom window closing handler at all) will destroy the window and all of its running callbacks instantly when the user closes it.

This can be bad for you, depending on your current Tkinter activity, and especially when using tkinter.after (periodic callbacks). You might be using a callback which processes some data and writes to disk... in that case, you obviously want the data writing to finish without being abruptly killed.

The best solution for that is to use a flag. So when the user requests window closing, you mark that as a flag, and then react to it.

(Note: I normally design GUIs as nicely encapsulated classes and separate worker threads, and I definitely don't use "global" (I use class instance variables instead), but this is meant to be a simple, stripped-down example to demonstrate how Tk abruptly kills your periodic callbacks when the user closes the window...)

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()

This code will show you that the WM_DELETE_WINDOW handler runs even while our custom periodic_call() is busy in the middle of work/loops!

We use some pretty exaggerated .after() values: 500 milliseconds. This is just meant to make it very easy for you to see the difference between closing while the periodic call is busy, or not... If you close while the numbers are updating, you will see that the WM_DELETE_WINDOW happened while your periodic call "was busy processing: True". If you close while the numbers are paused (meaning that the periodic callback isn't processing at that moment), you see that the close happened while it's "not busy".

In real-world usage, your .after() would use something like 30-100 milliseconds, to have a responsive GUI. This is just a demonstration to help you understand how to protect yourself against Tk's default "instantly interrupt all work when closing" behavior.

In summary: Make the WM_DELETE_WINDOW handler set a flag, and then check that flag periodically and manually .destroy() the window when it's safe (when your app is done with all work).

PS: You can also use WM_DELETE_WINDOW to ask the user if they REALLY want to close the window; and if they answer no, you don't set the flag. It's very simple. You just show a messagebox in your WM_DELETE_WINDOW and set the flag based on the user's answer.

Coastward answered 20/10, 2019 at 0:10 Comment(0)
C
2

You should use destroy() to close a tkinter window.

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

Explanation:

root.quit() The above line just Bypasses the root.mainloop() i.e root.mainloop() will still be running in background if quit() command is executed.

root.destroy() While destroy() command vanish out root.mainloop() i.e root.mainloop() stops.

So as you just want to quit the program so you should use root.destroy() as it will it stop the mainloop()`.

But if you want to run some infinite loop and you don't want to destroy your Tk window and want to execute some code after root.mainloop() line then you should use root.quit(). Ex:

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

root = Tk()
while True:
    Button(root, text="Quit", command=quit).pack()
    root.mainloop()
    #do something
Chignon answered 14/1, 2021 at 3:38 Comment(0)
J
2

The easiest code is:

from tkinter import *
window = Tk()

For hiding the window : window.withdraw()

For appearing the window : window.deiconify()

For exiting from the window : exit()

For exiting from the window(If you've made a .exe file) :

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

And of course you have to place a button and use the codes above in a function so you can type the function's name in the command part of the button

Just answered 2/9, 2021 at 12:19 Comment(0)
L
0

Try The Simple Version:

import tkinter

window = Tk()

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

window.mainloop()

Or If You Want To Add More Commands:

import tkinter

window = Tk()


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


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

window.mainloop()
Lobbyism answered 24/6, 2019 at 4:59 Comment(2)
Question is about the OS's X button for closing the window, not a regular button control.Leonialeonid
Is there a way to make tkitner window, top level i mean it dosen't hide if you click in the background of the desktop or for example a browser or any other app ?Achlamydeous
S
0

you can use:

root = Tk()
def func():
    print('not clossed')
root.protocol('wm_delete_window', func)
root.mainloop()
Sisile answered 20/3, 2022 at 18:30 Comment(0)
H
0
def on_closing():
    if messagebox.askokcancel("Quit", "would you like to quit"):
        window.destroy()


window.protocol("WM_DELETE_WINDOW", on_closing)

you can handle a window close event like this, if you wanna do something else just change the things that happen in the on_closing() function.

Hibiscus answered 11/10, 2022 at 4:2 Comment(0)
T
-1

i say a lot simpler way would be using the break command, like

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

OR use 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()
Trevatrevah answered 18/11, 2020 at 10:5 Comment(1)
As said in the comments of other answers, "Question is about the OS's X button for closing the window, not a regular button control."Dunbarton

© 2022 - 2024 — McMap. All rights reserved.