Wie gehe ich mit dem Fensterschließereignis (Benutzer klickt auf die Schaltfläche 'X') in einem Python Tkinter-Programm um?
Wie gehe ich mit dem Fensterschließereignis (Benutzer klickt auf die Schaltfläche 'X') in einem Python Tkinter-Programm um?
Antworten:
Tkinter unterstützt einen Mechanismus namens Protokollhandler . Hier bezieht sich der Begriff Protokoll auf die Interaktion zwischen der Anwendung und dem Fenstermanager. Das am häufigsten verwendete Protokoll wird aufgerufen WM_DELETE_WINDOW
und definiert, was passiert, wenn der Benutzer ein Fenster mithilfe des Fenstermanagers explizit schließt.
Mit der protocol
Methode können Sie einen Handler für dieses Protokoll installieren (das Widget muss ein Tk
oder ein Toplevel
Widget sein):
Hier haben Sie ein konkretes Beispiel:
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
es kein Submodul-Meldungsfeld. Ich benutzteimport tkMessageBox as messagebox
Matt hat eine klassische Modifikation des Schließknopfes gezeigt.
Die andere Möglichkeit besteht darin, dass die Schaltfläche zum Schließen das Fenster minimiert.
Sie können dieses Verhalten reproduziert , indem die mit Iconify Methode
der seine Protokoll - Methode der zweite Argument.
Hier ist ein Arbeitsbeispiel, das unter Windows 7 und 10 getestet wurde:
# 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 diesem Beispiel geben wir dem Benutzer zwei neue Exit-Optionen:
die klassische Datei → Beenden und auch die EscSchaltfläche.
Abhängig von der Tkinter-Aktivität und insbesondere bei Verwendung von Tkinter.after wird das Stoppen dieser Aktivität mit destroy()
- auch unter Verwendung von protocol (), einer Schaltfläche usw. - diese Aktivität stören (Fehler "während der Ausführung"), anstatt sie nur zu beenden . Die beste Lösung ist in fast allen Fällen die Verwendung einer Flagge. Hier ist ein einfaches, albernes Beispiel für die Verwendung (obwohl ich sicher bin, dass die meisten von Ihnen es nicht brauchen! :)
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()
Dies beendet die Grafikaktivität gut. Sie müssen nur running
an den richtigen Stellen nachsehen.
Ich möchte der Antwort von Apostolos dafür danken, dass er mich darauf aufmerksam gemacht hat. Hier ist ein viel detaillierteres Beispiel für Python 3 im Jahr 2019 mit einer klareren Beschreibung und einem Beispielcode.
destroy()
das Fenster und alle laufenden Rückrufe sofort zerstört werden, wenn der Benutzer es schließt (oder überhaupt keinen benutzerdefinierten Handler zum Schließen von Fenstern hat) .Dies kann abhängig von Ihrer aktuellen Tkinter-Aktivität und insbesondere bei der Verwendung tkinter.after
(regelmäßige Rückrufe) schlecht für Sie sein . Möglicherweise verwenden Sie einen Rückruf, der einige Daten verarbeitet und auf die Festplatte schreibt. In diesem Fall möchten Sie offensichtlich, dass das Schreiben der Daten beendet wird, ohne abrupt beendet zu werden.
Die beste Lösung dafür ist die Verwendung eines Flags. Wenn der Benutzer das Schließen des Fensters anfordert, markieren Sie dies als Flag und reagieren darauf.
(Hinweis: Normalerweise entwerfe ich GUIs als gut gekapselte Klassen und separate Arbeitsthreads, und ich verwende definitiv nicht "global" (ich verwende stattdessen Klasseninstanzvariablen), aber dies soll ein einfaches, reduziertes Beispiel sein, um dies zu demonstrieren wie Tk Ihre regelmäßigen Rückrufe abrupt beendet, wenn der Benutzer das Fenster schließt ...)
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()
Dieser Code zeigt Ihnen, dass der WM_DELETE_WINDOW
Handler auch ausgeführt wird, während unser Benutzer periodic_call()
mitten in der Arbeit / den Schleifen beschäftigt ist!
Wir verwenden einige ziemlich übertriebene .after()
Werte: 500 Millisekunden. Dies soll es Ihnen nur sehr leicht machen, den Unterschied zwischen dem Schließen während des periodischen Anrufs zu erkennen oder nicht ... Wenn Sie schließen, während die Nummern aktualisiert werden, werden Sie sehen, dass das WM_DELETE_WINDOW
passiert ist, während Ihr periodischer Anruf "war beschäftigt Verarbeitung: True ". Wenn Sie schließen, während die Nummern angehalten sind (was bedeutet, dass der periodische Rückruf in diesem Moment nicht verarbeitet wird), sehen Sie, dass das Schließen stattgefunden hat, während es "nicht besetzt" ist.
In der Praxis .after()
würden Sie etwa 30 bis 100 Millisekunden verwenden, um eine reaktionsschnelle Benutzeroberfläche zu erhalten. Dies ist nur eine Demonstration, die Ihnen hilft, zu verstehen, wie Sie sich vor dem Standardverhalten von Tk schützen können, "alle Arbeiten beim Schließen sofort unterbrechen".
Zusammenfassend: Lassen Sie den WM_DELETE_WINDOW
Handler ein Flag setzen und überprüfen Sie dieses Flag dann regelmäßig und manuell .destroy()
im Fenster, wenn es sicher ist (wenn Ihre App mit allen Arbeiten fertig ist).
PS: Sie können auch verwenden , WM_DELETE_WINDOW
um fragen den Benutzer , wenn sie wirklich das Fenster schließen wollen; und wenn sie mit nein antworten, setzen Sie das Flag nicht. Es ist sehr einfach. Sie zeigen einfach eine Nachrichtenbox in Ihrem an WM_DELETE_WINDOW
und setzen das Flag basierend auf der Antwort des Benutzers.
Versuchen Sie die einfache Version:
import tkinter
window = Tk()
closebutton = Button(window, text='X', command=window.destroy)
closebutton.pack()
window.mainloop()
Oder wenn Sie weitere Befehle hinzufügen möchten:
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()