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.