Come gestisco l'evento di chiusura della finestra (l'utente fa clic sul pulsante "X") in un programma Python Tkinter?
Come gestisco l'evento di chiusura della finestra (l'utente fa clic sul pulsante "X") in un programma Python Tkinter?
Risposte:
Tkinter supporta un meccanismo chiamato gestori di protocollo . Qui, il termine protocollo si riferisce all'interazione tra l'applicazione e il gestore delle finestre. Viene chiamato il protocollo più comunemente usato WM_DELETE_WINDOW
e viene utilizzato per definire cosa succede quando l'utente chiude esplicitamente una finestra utilizzando il gestore finestre.
È possibile utilizzare il protocol
metodo per installare un gestore per questo protocollo (il widget deve essere un Tk
o Toplevel
widget):
Ecco un esempio concreto:
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
non avevo una finestra di messaggio sottomodulo. Ho usatoimport tkMessageBox as messagebox
Matt ha mostrato una classica modifica del pulsante di chiusura.
L'altro è che il pulsante di chiusura minimizzi la finestra.
È possibile riprodurre questo comportamento facendo in modo che il metodo iconify
sia il secondo argomento del metodo protocollo .
Ecco un esempio funzionante, testato su Windows 7 e 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 questo esempio diamo all'utente due nuove opzioni di uscita:
il classico File → Esci e anche il Escpulsante.
A seconda dell'attività di Tkinter, e specialmente quando si utilizza Tkinter.after, interrompere questa attività con destroy()
- anche usando il protocollo (), un pulsante, ecc. - disturberà questa attività (errore "durante l'esecuzione") piuttosto che terminarla . La soluzione migliore in quasi tutti i casi è usare una bandiera. Ecco un esempio semplice e sciocco di come usarlo (anche se sono certo che molti di voi non ne hanno bisogno! :)
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()
Questo termina bene l'attività grafica. Devi solo controllare running
nei posti giusti.
Vorrei ringraziare la risposta di Apostolos per avermi portato alla mia attenzione. Ecco un esempio molto più dettagliato per Python 3 nell'anno 2019, con una descrizione più chiara e un codice di esempio.
destroy()
(o non avere un gestore di chiusura personalizzato della finestra) distruggerà immediatamente la finestra e tutti i suoi callback in esecuzione quando l'utente la chiude.Questo può essere dannoso per te, a seconda della tua attività Tkinter corrente, e specialmente quando usi tkinter.after
(callback periodici). È possibile che si stia utilizzando un callback che elabora alcuni dati e scrive sul disco ... in tal caso, ovviamente, si desidera che la scrittura dei dati termini senza essere interrotta bruscamente.
La migliore soluzione per questo è usare una bandiera. Pertanto, quando l'utente richiede la chiusura della finestra, contrassegnalo come flag e quindi reagisci ad esso.
(Nota: normalmente disegno le GUI come classi ben incapsulate e thread di lavoro separati, e sicuramente non uso "globale" (invece uso variabili di istanza di classe), ma questo è pensato per essere un semplice esempio ridotto per dimostrare come Tk uccide bruscamente i tuoi callback periodici quando l'utente chiude la finestra ...)
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()
Questo codice ti mostrerà che il WM_DELETE_WINDOW
gestore viene eseguito anche mentre la nostra abitudine periodic_call()
è impegnata nel mezzo di lavoro / cicli!
Usiamo alcuni .after()
valori piuttosto esagerati : 500 millisecondi. Questo serve solo per renderti molto facile vedere la differenza tra la chiusura mentre la chiamata periodica è occupata o no ... Se chiudi mentre i numeri si aggiornano, vedrai che è WM_DELETE_WINDOW
successo mentre la tua chiamata periodica "era elaborazione occupata: True ". Se chiudi mentre i numeri sono in pausa (il che significa che la richiamata periodica non viene elaborata in quel momento), vedi che la chiusura è avvenuta mentre "non è occupata".
Nell'uso nel mondo reale, .after()
useresti qualcosa come 30-100 millisecondi, per avere una GUI reattiva. Questa è solo una dimostrazione per aiutarti a capire come proteggerti dal comportamento predefinito di Tk "interrompere immediatamente tutto il lavoro alla chiusura".
In breve: fai in modo che il WM_DELETE_WINDOW
gestore imposti un flag, quindi controlla periodicamente e manualmente tale flag .destroy()
nella finestra quando è sicura (quando l'app ha terminato tutto il lavoro).
PS: puoi anche usare WM_DELETE_WINDOW
per chiedere all'utente se VUOI VERAMENTE chiudere la finestra; e se rispondono di no, non si imposta la bandiera. È molto semplice. Devi solo mostrare una finestra di messaggio nel tuo WM_DELETE_WINDOW
e impostare il flag in base alla risposta dell'utente.
Prova la versione semplice:
import tkinter
window = Tk()
closebutton = Button(window, text='X', command=window.destroy)
closebutton.pack()
window.mainloop()
O se si desidera aggiungere altri comandi:
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()