Bagaimana cara saya menangani acara tutup jendela di Tkinter?

131

Bagaimana cara saya menangani acara tutup jendela (pengguna mengklik tombol 'X') dalam program Python Tkinter?

Matt Gregory
sumber

Jawaban:

178

Tkinter mendukung mekanisme yang disebut penangan protokol . Di sini, istilah protokol mengacu pada interaksi antara aplikasi dan window manager. Protokol yang paling umum digunakan disebut WM_DELETE_WINDOW, dan digunakan untuk menentukan apa yang terjadi ketika pengguna secara eksplisit menutup jendela menggunakan window manager.

Anda dapat menggunakan protocolmetode ini untuk menginstal handler untuk protokol ini (widget harus a Tkatau Toplevelwidget):

Di sini Anda memiliki contoh nyata:

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
sumber
2
Jika Anda menggunakan sesuatu seperti Twisted yang mempertahankan loop peristiwa secara mandiri atau Tkinter (misalnya: objek reaktor twisted) pastikan loop utama luar dihentikan dengan smenatics apa pun yang disediakan untuk tujuan itu (mis: reactor.stop () untuk twisted)
Brian Jack
4
Pada Python 2.7 saya di Windows, Tkintertidak memiliki kotak pesan submodule. Saya menggunakanimport tkMessageBox as messagebox
IronManMark20
Saya pikir Anda harus memberitahukan bahwa Anda menyalin jawaban dan kode ini dari seseorang / di mana lagi.
Christian Dean
1
Saya tidak tahu, itu bukan kode yang semula saya posting.
Matt Gregory
Tidak bekerja untuk saya. Itu tidak mengubah reaksi kacau Python klasik untuk gangguan grafis ketika seseorang menutup jendela (misalnya dengan Alt + F4).
Apostolos
29

Matt telah menunjukkan satu modifikasi klasik dari tombol tutup.
Yang lain adalah memiliki tombol tutup meminimalkan jendela.
Anda dapat mereproduksi perilaku ini dengan memiliki metode iconify
menjadi argumen kedua metode protokol .

Berikut ini contoh yang berfungsi, diuji pada Windows 7 & 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()

Dalam contoh ini kami memberi pengguna dua opsi keluar baru:
File klasik → Keluar, dan juga Esctombol.

Abe Jujur
sumber
14

Bergantung pada aktivitas Tkinter, dan terutama ketika menggunakan Tkinter.setelah, menghentikan aktivitas ini dengan destroy()- bahkan dengan menggunakan protokol (), sebuah tombol, dll. - akan mengganggu aktivitas ini (kesalahan "saat menjalankan") daripada hanya menghentikannya . Solusi terbaik di hampir setiap kasus adalah menggunakan bendera. Ini adalah contoh sederhana dan konyol tentang cara menggunakannya (walaupun saya yakin sebagian besar dari Anda tidak membutuhkannya! :)

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

Ini mengakhiri aktivitas grafik dengan baik. Anda hanya perlu memeriksa runningdi tempat yang tepat.

Apostolos
sumber
4

Saya ingin mengucapkan terima kasih atas jawaban Apostolos yang telah menyampaikan ini kepada saya. Berikut adalah contoh yang jauh lebih terperinci untuk Python 3 di tahun 2019, dengan deskripsi dan contoh kode yang lebih jelas.


Waspadalah terhadap kenyataan bahwa destroy()(atau tidak memiliki penangan penutupan jendela khusus sama sekali) akan menghancurkan jendela dan semua callback yang berjalan instan ketika pengguna menutupnya.

Ini bisa berdampak buruk bagi Anda, tergantung pada aktivitas Tkinter Anda saat ini, dan terutama saat menggunakan tkinter.after (panggilan balik berkala). Anda mungkin menggunakan panggilan balik yang memproses beberapa data dan menulis ke disk ... dalam hal ini, Anda jelas ingin penulisan data selesai tanpa terbunuh secara tiba-tiba.

Solusi terbaik untuk itu adalah menggunakan bendera. Jadi, ketika pengguna meminta penutupan jendela, Anda menandainya sebagai bendera, dan kemudian bereaksi.

(Catatan: Saya biasanya mendesain GUI sebagai kelas yang dienkapsulasi dengan baik dan utas pekerja terpisah, dan saya jelas tidak menggunakan "global" (sebagai gantinya saya menggunakan variabel instance kelas), tetapi ini dimaksudkan sebagai contoh sederhana yang tidak perlu untuk menunjukkan bagaimana Tk tiba-tiba membunuh panggilan balik berkala Anda ketika pengguna menutup jendela ...)

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

Kode ini akan menunjukkan kepada Anda bahwa WM_DELETE_WINDOWpawang berjalan bahkan saat kebiasaan kamiperiodic_call() sedang sibuk di tengah-tengah kerja / loop!

Kami menggunakan beberapa .after()nilai yang cukup berlebihan : 500 milidetik. Ini hanya dimaksudkan untuk membuatnya sangat mudah bagi Anda untuk melihat perbedaan antara menutup sementara panggilan periodik sedang sibuk, atau tidak ... Jika Anda menutup ketika nomor sedang memperbarui, Anda akan melihat bahwa itu WM_DELETE_WINDOWterjadi ketika panggilan periodik Anda "tadinya proses sibuk: Benar ". Jika Anda menutup sementara nomor dijeda (berarti bahwa panggilan balik periodik tidak diproses pada saat itu), Anda melihat bahwa penutupan terjadi ketika sedang "tidak sibuk".

Dalam penggunaan di dunia nyata, Anda .after()akan menggunakan sesuatu seperti 30-100 milidetik, untuk memiliki GUI yang responsif. Ini hanya sebuah demonstrasi untuk membantu Anda memahami cara melindungi diri Anda dari perilaku standar Tk "segera hentikan semua pekerjaan saat menutup".

Singkatnya: Buat WM_DELETE_WINDOWpawang mengatur bendera, dan kemudian periksa bendera itu secara berkala dan secara manual .destroy()jendela itu aman (ketika aplikasi Anda selesai dengan semua pekerjaan).

PS: Anda juga dapat menggunakan WM_DELETE_WINDOWuntuk bertanya kepada pengguna apakah mereka BENAR-BENAR ingin menutup jendela; dan jika mereka menjawab tidak, Anda tidak mengatur benderanya. Ini sangat sederhana. Anda hanya menunjukkan kotak pesan di kotak Anda WM_DELETE_WINDOWdan mengatur bendera berdasarkan jawaban pengguna.

Mitch McMabers
sumber
1

Coba Versi Sederhana:

import tkinter

window = Tk()

closebutton = Button(window, text='X', command=window.destroy)
closebutton.pack()

window.mainloop()

Atau Jika Anda Ingin Menambahkan Perintah Lebih Banyak:

import tkinter

window = Tk()


def close():
    window.destroy()
    #More Functions


closebutton = Button(window, text='X', command=close)
closebutton.pack()

window.mainloop()
Studi SF12
sumber
Pertanyaannya adalah tentang tombol X OS untuk menutup jendela, bukan kontrol tombol biasa.
user1318499
-1
from tkinter import*
root=Tk()
exit_button=Button(root,text="X",command=root.quit)
root.mainloop()
Tirth Anand
sumber