Je tiens à remercier la réponse d'Apostolos pour avoir attiré mon attention sur ce point. Voici un exemple beaucoup plus détaillé pour Python 3 en l'an 2019, avec une description plus claire et un exemple de code.
Attention au fait que destroy()
(ou ne pas avoir du tout de gestionnaire de fermeture de fenêtre personnalisé) détruira la fenêtre et tous ses rappels en cours d'exécution instantanément lorsque l'utilisateur le ferme.
Cela peut être mauvais pour vous, en fonction de votre activité Tkinter actuelle, et en particulier lorsque vous utilisez tkinter.after
(rappels périodiques). Vous pouvez utiliser un callback qui traite des données et les écrit sur le disque... dans ce cas, vous voulez évidemment que l'écriture des données se termine sans être brusquement interrompu.
La meilleure solution pour cela est d'utiliser un drapeau. Ainsi, lorsque l'utilisateur demande la fermeture d'une fenêtre, vous le marquez comme un drapeau, puis vous réagissez à cette demande.
(Note : Je conçois normalement les interfaces graphiques comme des classes joliment encapsulées et des threads de travail séparés, et je n'utilise absolument pas "global" (j'utilise plutôt des variables d'instance de classe), mais ceci est censé être un exemple simple et dépouillé pour démontrer comment Tk tue brusquement vos callbacks périodiques lorsque l'utilisateur ferme la fenêtre...)
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()
Ce code vous montrera que le WM_DELETE_WINDOW
s'exécute même si notre gestionnaire personnalisé periodic_call()
est occupé au milieu du travail/des boucles !
Nous utilisons certains assez exagérés .after()
valeurs : 500 millisecondes. Ceci est juste destiné à vous permettre de voir très facilement la différence entre fermer pendant que l'appel périodique est occupé, ou pas... Si vous fermez pendant que les chiffres sont en train de se mettre à jour, vous verrez que l'option WM_DELETE_WINDOW
est arrivé tandis que votre appel périodique "était occupé à traiter : True". Si vous fermez alors que les numéros sont en pause (ce qui signifie que le rappel périodique n'est pas en cours de traitement à ce moment-là), vous verrez que la fermeture a eu lieu alors que le système n'était pas occupé.
Dans le monde réel, votre .after()
utiliserait quelque chose comme 30-100 millisecondes, pour avoir une interface graphique réactive. Ceci n'est qu'une démonstration pour vous aider à comprendre comment vous protéger contre le comportement par défaut de Tk "interrompre instantanément tout travail à la fermeture".
En résumé, faites le WM_DELETE_WINDOW
met un drapeau, puis vérifie ce drapeau périodiquement et manuellement. .destroy()
la fenêtre quand c'est sûr (quand votre application a terminé tout le travail).
PS : Vous pouvez également utiliser WM_DELETE_WINDOW
à demandez à l'utilisateur s'il veut VRAIMENT fermer la fenêtre ; et s'il répond non, vous n'activez pas le drapeau. C'est très simple. Il suffit d'afficher une boîte de message dans votre WM_DELETE_WINDOW
et mettre le drapeau en fonction de la réponse de l'utilisateur.