Làm cách nào để xử lý sự kiện đóng cửa sổ trong Tkinter?


130

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:


178

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_WINDOWvà đượ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 protocolphương pháp để cài đặt trình xử lý cho giao thức này (tiện ích phải là một Tkhoặc Topleveltiệ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()

2
Nếu bạn đang sử dụng một cái gì đó như Twisted duy trì vòng lặp sự kiện một cách độc lập hoặc Tkinter (ví dụ: đối tượng lò phản ứng của xoắn), hãy đảm bảo rằng vòng lặp chính bên ngoài được dừng với bất kỳ thông tin xấu nào mà nó cung cấp cho mục đích đó (ví dụ: Reactor.stop () cho xoắn)
Brian Jack

4
Trên Python 2.7 của tôi trên Windows, Tkinterkhông có hộp thông báo mô hình con. Tôi đã sử dụngimport tkMessageBox as messagebox
IronManMark20

Tôi nghĩ bạn nên biết rằng bạn đã sao chép câu trả lời và mã này từ ai đó / nơi khác.
Christian Dean

1
Tôi không biết, đó không phải là mã mà tôi đã đăng ban đầu.
Matt Gregory

Không làm việc cho tôi. Nó không thay đổi phản ứng hỗn loạn của Python cổ điển sang sự gián đoạn của đồ họa khi một cửa sổ đóng cứng (ví dụ với Alt + F4).
Apostolos

29

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.


12

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.


4

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.


Cảnh giác với thực tế là 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_WINDOWtrì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_WINDOWxả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_WINDOWtrì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_WINDOWvà đặt cờ dựa trên câu trả lời của người dùng.


1

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

Câu hỏi là về nút X của HĐH để đóng cửa sổ, không phải điều khiển nút thông thường.
dùng1318499

-1
from tkinter import*
root=Tk()
exit_button=Button(root,text="X",command=root.quit)
root.mainloop()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.