Come gestisco l'evento di chiusura della finestra in Tkinter?


131

Come gestisco l'evento di chiusura della finestra (l'utente fa clic sul pulsante "X") in un programma Python Tkinter?

Risposte:


178

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_WINDOWe viene utilizzato per definire cosa succede quando l'utente chiude esplicitamente una finestra utilizzando il gestore finestre.

È possibile utilizzare il protocolmetodo per installare un gestore per questo protocollo (il widget deve essere un Tko Toplevelwidget):

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

2
Se stai usando qualcosa come Twisted che mantiene un loop di eventi in modo indipendente o Tkinter (ad esempio: oggetto del reattore attorcigliato) assicurati che l'anello principale esterno sia arrestato con qualsiasi smenatics a tale scopo (ad esempio: reattor.stop () per twisted)
Brian Jack,

4
Sul mio Python 2.7 su Windows, Tkinternon avevo una finestra di messaggio sottomodulo. Ho usatoimport tkMessageBox as messagebox
IronManMark20

Penso che dovresti far sapere che hai copiato questa risposta e questo codice da qualcuno / dove altro.
Christian Dean,

1
Non lo so, non è il codice che ho pubblicato in origine.
Matt Gregory,

Non funziona per me. Non cambia la reazione caotica di Python classica all'interruzione della grafica quando si chiude la finestra (ad es. Con Alt + F4).
Apostolos,

29

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.


14

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 runningnei posti giusti.


4

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.


Fare attenzione al fatto che 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_WINDOWgestore 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_WINDOWsuccesso 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_WINDOWgestore 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_WINDOWper 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_WINDOWe impostare il flag in base alla risposta dell'utente.


1

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

La domanda riguarda il pulsante X del sistema operativo per chiudere la finestra, non un normale controllo dei pulsanti.
user1318499

-1
from tkinter import*
root=Tk()
exit_button=Button(root,text="X",command=root.quit)
root.mainloop()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.