Jak obsłużyć zdarzenie zamknięcia okna (użytkownik kliknie przycisk „X”) w programie Python Tkinter?
Jak obsłużyć zdarzenie zamknięcia okna (użytkownik kliknie przycisk „X”) w programie Python Tkinter?
Odpowiedzi:
Tkinter obsługuje mechanizm zwany programami obsługi protokołów . Tutaj termin protokół odnosi się do interakcji między aplikacją a menedżerem okien. Najczęściej używany protokół jest nazywany WM_DELETE_WINDOW
i służy do definiowania tego, co się dzieje, gdy użytkownik jawnie zamyka okno za pomocą menedżera okien.
Możesz użyć tej protocol
metody, aby zainstalować program obsługi dla tego protokołu (widget musi być widgetem Tk
lub Toplevel
):
Tutaj masz konkretny przykład:
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
nie miałem pola wiadomości podmodułu. Użyłemimport tkMessageBox as messagebox
Matt pokazał jedną klasyczną modyfikację przycisku zamykającego.
Drugi to przycisk zamykania, który minimalizuje okno.
Możesz odtworzyć to zachowanie, mając metodę iconify
jako drugi argument metody protokołu .
Oto działający przykład, przetestowany na Windows 7 i 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()
W tym przykładzie dajemy użytkownikowi dwie nowe opcje wyjścia:
klasyczną Plik → Zakończ, a także Escprzycisk.
W zależności od aktywności Tkinter, a zwłaszcza podczas korzystania z Tkinter.after, zatrzymanie tej czynności za pomocą destroy()
- nawet przy użyciu protokołu (), przycisku itp. - zakłóci tę czynność (błąd „podczas wykonywania”), a nie po prostu ją zakończy . Prawie w każdym przypadku najlepszym rozwiązaniem jest użycie flagi. Oto prosty, głupiutki przykład, jak go używać (chociaż jestem pewien, że większość z Was go nie potrzebuje! :)
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()
To ładnie kończy aktywność graficzną. Musisz tylko sprawdzić running
we właściwym miejscu (miejscach).
Chciałbym podziękować odpowiedzi Apostolos za zwrócenie mi na to uwagi. Oto znacznie bardziej szczegółowy przykład dla Pythona 3 w roku 2019, z jaśniejszym opisem i przykładowym kodem.
destroy()
(lub w ogóle brak niestandardowego modułu obsługi zamykania okna) zniszczy okno i wszystkie jego uruchomione wywołania zwrotne natychmiast po zamknięciu go przez użytkownika.Może to być dla Ciebie złe, w zależności od Twojej bieżącej aktywności Tkinter, a zwłaszcza podczas korzystania z tkinter.after
(okresowe wywołania zwrotne). Być może używasz wywołania zwrotnego, które przetwarza niektóre dane i zapisuje na dysku ... w takim przypadku chcesz oczywiście zakończyć zapisywanie danych bez nagłego zabicia.
Najlepszym rozwiązaniem jest użycie flagi. Więc kiedy użytkownik zażąda zamknięcia okna, zaznaczasz to jako flagę, a następnie reagujesz na to.
(Uwaga: zwykle projektuję GUI jako ładnie zamknięte klasy i oddzielne wątki robocze i zdecydowanie nie używam „globalnego” (zamiast tego używam zmiennych instancji klas), ale ma to być prosty, okrojony przykład pokazujący jak Tk nagle zabija twoje okresowe wywołania zwrotne, gdy użytkownik zamyka okno ...)
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()
Ten kod pokaże ci, że program WM_DELETE_WINDOW
obsługi działa nawet wtedy, gdy nasz niestandardowy periodic_call()
jest zajęty w środku pracy / pętli!
Używamy dość przesadzonych .after()
wartości: 500 milisekund. Ma to tylko na celu ułatwienie Ci dostrzeżenia różnicy między zamknięciem, gdy okresowa rozmowa jest zajęta, czy nie… Jeśli zamkniesz konto podczas aktualizacji numerów, zobaczysz, że WM_DELETE_WINDOW
stało się to, gdy Twoje połączenie okresowe było zajęte przetwarzanie: prawda ”. Jeśli zamkniesz, gdy numery są wstrzymane (co oznacza, że okresowe oddzwanianie nie jest w tej chwili przetwarzane), zobaczysz, że zamknięcie nastąpiło, gdy nie jest zajęty.
W prawdziwym świecie .after()
potrzebowałbyś około 30-100 milisekund, aby mieć responsywny GUI. To tylko demonstracja, która pomoże ci zrozumieć, jak chronić się przed domyślnym zachowaniem Tk „natychmiast przerywaj całą pracę przy zamykaniu”.
Podsumowując: spraw, aby program WM_DELETE_WINDOW
obsługi ustawił flagę, a następnie okresowo sprawdzaj tę flagę i ręcznie sprawdzaj .destroy()
okno, gdy jest to bezpieczne (gdy aplikacja jest zakończona z całą pracą).
PS: Możesz również użyć, WM_DELETE_WINDOW
aby zapytać użytkownika, czy NAPRAWDĘ chce zamknąć okno; a jeśli odpowiedzą „nie”, nie ustawiasz flagi. To jest bardzo proste. Po prostu pokazujesz skrzynkę wiadomości w swoim WM_DELETE_WINDOW
i ustawiasz flagę na podstawie odpowiedzi użytkownika.
Wypróbuj prostą wersję:
import tkinter
window = Tk()
closebutton = Button(window, text='X', command=window.destroy)
closebutton.pack()
window.mainloop()
Lub jeśli chcesz dodać więcej poleceń:
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()