Comment gérer l'événement de fermeture de fenêtre (l'utilisateur clique sur le bouton «X») dans un programme Python Tkinter?
Comment gérer l'événement de fermeture de fenêtre (l'utilisateur clique sur le bouton «X») dans un programme Python Tkinter?
Réponses:
Tkinter prend en charge un mécanisme appelé gestionnaires de protocole . Ici, le terme protocole fait référence à l'interaction entre l'application et le gestionnaire de fenêtres. Le protocole le plus couramment utilisé est appelé WM_DELETE_WINDOW
et sert à définir ce qui se passe lorsque l'utilisateur ferme explicitement une fenêtre à l'aide du gestionnaire de fenêtres.
Vous pouvez utiliser la protocol
méthode pour installer un gestionnaire pour ce protocole (le widget doit être un widget Tk
ou Toplevel
):
Voici un exemple concret:
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()
Tkinter
je n'avais pas de boîte de message de sous-module. J'ai utiliséimport tkMessageBox as messagebox
Matt a montré une modification classique du bouton de fermeture.
L'autre est que le bouton de fermeture minimise la fenêtre.
Vous pouvez reproduire ce comportement en ayant la méthode iconify
comme deuxième argument de la méthode de protocole .
Voici un exemple fonctionnel, testé sur Windows 7 et 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()
Dans cet exemple, nous donnons à l'utilisateur deux nouvelles options de sortie:
le classique Fichier → Quitter, ainsi que le Escbouton.
En fonction de l'activité de Tkinter, et en particulier lors de l'utilisation de Tkinter.after, arrêter cette activité avec destroy()
- même en utilisant protocol (), un bouton, etc. - perturbera cette activité (erreur "lors de l'exécution") plutôt que de simplement la terminer . La meilleure solution dans presque tous les cas est d'utiliser un drapeau. Voici un exemple simple et idiot de comment l'utiliser (même si je suis certain que la plupart d'entre vous n'en ont pas besoin! :)
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()
Cela termine bien l'activité graphique. Il vous suffit de vérifier running
au (x) bon (s) endroit (s).
Je tiens à remercier la réponse d'Apostolos d'avoir porté cela à mon attention. Voici un exemple beaucoup plus détaillé pour Python 3 en 2019, avec une description plus claire et un exemple de code.
destroy()
(ou ne pas avoir de gestionnaire de fermeture de fenêtre personnalisé du tout) détruira la fenêtre et tous ses rappels en cours d'exécution instantanément lorsque l'utilisateur la ferme.Cela peut être mauvais pour vous, en fonction de votre activité Tkinter actuelle, et en particulier lors de l'utilisation tkinter.after
(rappels périodiques). Vous pourriez utiliser un callback qui traite certaines données et écrit sur le disque ... dans ce cas, vous voulez évidemment que l'écriture des données se termine sans être brusquement tuée.
La meilleure solution pour cela est d'utiliser un drapeau. Ainsi, lorsque l'utilisateur demande la fermeture de la fenêtre, vous marquez cela comme un indicateur, puis vous y réagissez.
(Remarque: je conçois normalement les interfaces graphiques comme des classes bien encapsulées et des threads de travail séparés, et je n'utilise certainement pas "global" (j'utilise des variables d'instance de classe à la place), mais cela est censé être un exemple simple et dépouillé pour démontrer comment Tk tue brusquement vos rappels 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
gestionnaire s'exécute même lorsque notre custom periodic_call()
est occupé au milieu du travail / des boucles!
Nous utilisons des .after()
valeurs assez exagérées : 500 millisecondes. Ceci est simplement destiné à vous permettre de voir très facilement la différence entre la fermeture pendant que l'appel périodique est occupé, ou non ... Si vous fermez pendant que les numéros sont mis à jour, vous verrez que WM_DELETE_WINDOW
cela s'est produit pendant votre appel périodique "était traitement occupé: Vrai ". Si vous fermez pendant 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 voyez que la fermeture s'est produite alors qu'il n'est "pas occupé".
Dans le monde réel, vous .after()
utiliseriez quelque chose comme 30 à 100 millisecondes pour avoir une interface graphique réactive. Ceci est juste une démonstration pour vous aider à comprendre comment vous protéger contre le comportement par défaut de Tk "interrompre instantanément tout travail lors de la fermeture".
En résumé: faites en WM_DELETE_WINDOW
sorte que le gestionnaire définisse un indicateur, puis vérifiez cet indicateur périodiquement et manuellement dans .destroy()
la fenêtre lorsqu'elle est en sécurité (lorsque votre application a terminé son travail).
PS: Vous pouvez également utiliser WM_DELETE_WINDOW
pour demander à l'utilisateur s'il veut VRAIMENT fermer la fenêtre; et s'ils répondent non, vous ne définissez pas le drapeau. C'est très simple. Il vous suffit d'afficher une boîte de message dans votre WM_DELETE_WINDOW
et de définir l'indicateur en fonction de la réponse de l'utilisateur.
Essayez la version simple:
import tkinter
window = Tk()
closebutton = Button(window, text='X', command=window.destroy)
closebutton.pack()
window.mainloop()
Ou si vous souhaitez ajouter plus de commandes:
import tkinter
window = Tk()
def close():
window.destroy()
#More Functions
closebutton = Button(window, text='X', command=close)
closebutton.pack()
window.mainloop()
from tkinter import*
root=Tk()
exit_button=Button(root,text="X",command=root.quit)
root.mainloop()