Làm cách nào để xử lý sự kiện đóng cửa sổ (người dùng nhấp vào nút 'X') trong chương trình Python Tkinter?
Làm cách nào để xử lý sự kiện đóng cửa sổ (người dùng nhấp vào nút 'X') trong chương trình Python Tkinter?
Câu trả lời:
Tkinter hỗ trợ một cơ chế gọi là xử lý giao thức . Ở đây, thuật ngữ giao thức đề cập đến sự tương tác giữa ứng dụng và trình quản lý cửa sổ. Giao thức được sử dụng phổ biến nhất được gọi WM_DELETE_WINDOW
và được sử dụng để xác định điều gì sẽ xảy ra khi người dùng đóng cửa sổ một cách rõ ràng bằng trình quản lý cửa sổ.
Bạn có thể sử dụng protocol
phương pháp để cài đặt trình xử lý cho giao thức này (tiện ích phải là một Tk
hoặc Toplevel
tiện ích con):
Ở đây bạn có một ví dụ cụ thể:
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
không có hộp thông báo mô hình con. Tôi đã sử dụngimport tkMessageBox as messagebox
Matt đã cho thấy một sửa đổi cổ điển của nút đóng.
Khác là có nút đóng tối thiểu hóa cửa sổ.
Bạn có thể tái tạo hành vi này bằng cách sử dụng phương thức iconify
là đối số thứ hai của phương thức giao thức .
Đây là một ví dụ hoạt động, được thử nghiệm trên 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()
Trong ví dụ này, chúng tôi cung cấp cho người dùng hai tùy chọn thoát mới:
Tệp cổ điển → Thoát và cũng là Escnút.
Tùy thuộc vào hoạt động Tkinter và đặc biệt là khi sử dụng Tkinter.after, việc dừng hoạt động này bằng destroy()
- ngay cả bằng cách sử dụng giao thức (), nút, v.v. - sẽ làm phiền hoạt động này ("trong khi thực thi") . Giải pháp tốt nhất trong hầu hết mọi trường hợp là sử dụng cờ. Đây là một ví dụ đơn giản, ngớ ngẩn về cách sử dụng nó (mặc dù tôi chắc chắn rằng hầu hết các bạn không cần nó! :)
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()
Điều này chấm dứt hoạt động đồ họa độc đáo. Bạn chỉ cần kiểm tra running
đúng nơi.
Tôi muốn cảm ơn câu trả lời của Apostolos vì đã chú ý đến điều này. Đây là một ví dụ chi tiết hơn nhiều cho Python 3 vào năm 2019, với một mô tả và mã ví dụ rõ ràng hơn.
destroy()
(hoặc không có trình xử lý đóng cửa sổ tùy chỉnh nào) sẽ phá hủy cửa sổ và tất cả các cuộc gọi lại đang chạy của nó ngay lập tức khi người dùng đóng nó.Điều này có thể không tốt cho bạn, tùy thuộc vào hoạt động Tkinter hiện tại của bạn và đặc biệt là khi sử dụng tkinter.after
(gọi lại định kỳ). Bạn có thể đang sử dụng một cuộc gọi lại xử lý một số dữ liệu và ghi vào đĩa ... trong trường hợp đó, rõ ràng bạn muốn việc ghi dữ liệu kết thúc mà không bị giết đột ngột.
Giải pháp tốt nhất cho điều đó là sử dụng cờ. Vì vậy, khi người dùng yêu cầu đóng cửa sổ, bạn đánh dấu đó là cờ và sau đó phản ứng với nó.
(Lưu ý: Tôi thường thiết kế GUI như các lớp được đóng gói độc đáo và các luồng công nhân riêng biệt và tôi chắc chắn không sử dụng "toàn cầu" (thay vào đó tôi sử dụng các biến thể hiện của lớp) Làm thế nào Tk đột ngột giết các cuộc gọi lại định kỳ của bạn khi người dùng đóng cửa sổ ...)
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()
Mã này sẽ cho bạn thấy rằng WM_DELETE_WINDOW
trình xử lý chạy ngay cả khi tùy chỉnh của chúng tôi periodic_call()
bận giữa công việc / vòng lặp!
Chúng tôi sử dụng một số .after()
giá trị phóng đại khá : 500 mili giây. Điều này chỉ nhằm giúp bạn dễ dàng nhận thấy sự khác biệt giữa việc đóng trong khi cuộc gọi định kỳ có bận hay không ... Nếu bạn đóng trong khi các số đang cập nhật, bạn sẽ thấy điều đó WM_DELETE_WINDOW
xảy ra trong khi cuộc gọi định kỳ của bạn "là bận xử lý: Đúng ". Nếu bạn đóng trong khi các số bị tạm dừng (có nghĩa là cuộc gọi lại định kỳ không xử lý tại thời điểm đó), bạn sẽ thấy rằng việc đóng xảy ra trong khi "không bận".
Trong sử dụng trong thế giới thực, bạn .after()
sẽ sử dụng khoảng 30 đến 100 mili giây để có GUI đáp ứng. Đây chỉ là một minh chứng để giúp bạn hiểu cách bảo vệ bản thân trước hành vi "ngắt ngay lập tức mọi hoạt động khi đóng" của Tk.
Tóm lại: Đặt WM_DELETE_WINDOW
trình xử lý đặt cờ và sau đó kiểm tra cờ đó theo định kỳ và theo cách thủ công .destroy()
cửa sổ khi an toàn (khi ứng dụng của bạn hoàn tất mọi công việc).
PS: Bạn cũng có thể sử dụng WM_DELETE_WINDOW
để hỏi người dùng nếu họ THỰC SỰ muốn đóng cửa sổ; và nếu họ trả lời không, bạn không đặt cờ. Nó rất đơn giản. Bạn chỉ cần hiển thị một hộp thông báo trong WM_DELETE_WINDOW
và đặt cờ dựa trên câu trả lời của người dùng.
Hãy thử phiên bản đơn giản:
import tkinter
window = Tk()
closebutton = Button(window, text='X', command=window.destroy)
closebutton.pack()
window.mainloop()
Hoặc nếu bạn muốn thêm các lệnh khác:
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()