Wie gehe ich mit dem Fensterschließereignis in Tkinter um?

130

Wie gehe ich mit dem Fensterschließereignis (Benutzer klickt auf die Schaltfläche 'X') in einem Python Tkinter-Programm um?

Matt Gregory
quelle

Antworten:

178

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_WINDOWund definiert, was passiert, wenn der Benutzer ein Fenster mithilfe des Fenstermanagers explizit schließt.

Mit der protocolMethode können Sie einen Handler für dieses Protokoll installieren (das Widget muss ein Tkoder ein ToplevelWidget 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()
Matt Gregory
quelle
2
Wenn Sie so etwas wie Twisted - verwenden , die eine Ereignisschleife hält unabhängig oder Tkinter (zB: verdrillt Reaktor Objekt) sicherstellen , dass die äußere Hauptschleife gestoppt mit dem, was smenatics es bietet zu diesem Zweck (zB: reactor.stop () für Twisted)
Brian Jack
4
Auf meinem Python 2.7 unter Windows gab Tkinteres kein Submodul-Meldungsfeld. Ich benutzteimport tkMessageBox as messagebox
IronManMark20
Ich denke, Sie sollten bekannt geben, dass Sie diese Antwort und den Code von jemandem / wo sonst kopiert haben.
Christian Dean
1
Ich weiß nicht, das ist nicht der Code, den ich ursprünglich gepostet habe.
Matt Gregory
Funktioniert bei mir nicht Es ändert nichts an der chaotischen Reaktion des klassischen Python auf die Unterbrechung von Grafiken, wenn man das Fenster hart schließt (z. B. mit Alt + F4).
Apostolos
29

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.

Ehrlich abe
quelle
12

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 runningan den richtigen Stellen nachsehen.

Apostolos
quelle
4

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.


Beachten Sie, dass 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_WINDOWHandler 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_WINDOWpassiert 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_WINDOWHandler 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_WINDOWum 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_WINDOWund setzen das Flag basierend auf der Antwort des Benutzers.

Mitch McMabers
quelle
1

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()
SF12-Studie
quelle
Bei der Frage geht es um die X-Taste des Betriebssystems zum Schließen des Fensters, nicht um eine normale Tastensteuerung.
user1318499
-1
from tkinter import*
root=Tk()
exit_button=Button(root,text="X",command=root.quit)
root.mainloop()
Tirth Anand
quelle