Bagaimana cara saya menangani acara tutup jendela (pengguna mengklik tombol 'X') dalam program Python Tkinter?
Bagaimana cara saya menangani acara tutup jendela (pengguna mengklik tombol 'X') dalam program Python Tkinter?
Jawaban:
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 protocol
metode ini untuk menginstal handler untuk protokol ini (widget harus a Tk
atau Toplevel
widget):
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()
Tkinter
tidak memiliki kotak pesan submodule. Saya menggunakanimport tkMessageBox as messagebox
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.
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 running
di tempat yang tepat.
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.
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_WINDOW
pawang 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_WINDOW
terjadi 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_WINDOW
pawang 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_WINDOW
untuk 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_WINDOW
dan mengatur bendera berdasarkan jawaban pengguna.
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()
from tkinter import*
root=Tk()
exit_button=Button(root,text="X",command=root.quit)
root.mainloop()