ฉันจะจัดการกับเหตุการณ์ปิดหน้าต่างได้อย่างไร (ผู้ใช้คลิกปุ่ม 'X') ในโปรแกรม Python Tkinter
ฉันจะจัดการกับเหตุการณ์ปิดหน้าต่างได้อย่างไร (ผู้ใช้คลิกปุ่ม 'X') ในโปรแกรม Python Tkinter
คำตอบ:
Tkinter สนับสนุนกลไกที่เรียกว่าจัดการโปรโตคอล ในที่นี้คำว่าโปรโตคอลหมายถึงการโต้ตอบระหว่างแอปพลิเคชันและตัวจัดการหน้าต่าง โปรโตคอลที่ใช้บ่อยที่สุดเรียกว่าWM_DELETE_WINDOW
และใช้เพื่อกำหนดสิ่งที่เกิดขึ้นเมื่อผู้ใช้ปิดหน้าต่างอย่างชัดเจนโดยใช้ตัวจัดการหน้าต่าง
คุณสามารถใช้protocol
วิธีการติดตั้งตัวจัดการสำหรับโปรโตคอลนี้ (วิดเจ็ตต้องเป็นTk
หรือToplevel
วิดเจ็ต):
คุณมีตัวอย่างที่เป็นรูปธรรม:
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
ไม่มีกล่องข้อความโมดูลย่อย ฉันใช้import tkMessageBox as messagebox
Matt ได้แสดงการปรับเปลี่ยนปุ่มปิดแบบคลาสสิกอย่างหนึ่ง
อีกปุ่มหนึ่งคือการมีปุ่มปิดเพื่อย่อหน้าต่าง
คุณสามารถสร้างพฤติกรรมนี้ซ้ำได้โดยกำหนดให้เมธอดiconify
เป็นอาร์กิวเมนต์ที่สองของโปรโตคอลเมธอด
นี่คือตัวอย่างการทำงานที่ทดสอบบน 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()
ในตัวอย่างนี้เราให้ตัวเลือกการออกใหม่แก่ผู้ใช้สองตัวเลือก:
ไฟล์คลาสสิก→ออกและEscปุ่ม
ขึ้นอยู่กับกิจกรรม Tkinter และโดยเฉพาะอย่างยิ่งเมื่อใช้ Tkinter หลังจากนั้นการหยุดกิจกรรมนี้ด้วยdestroy()
- แม้จะใช้โปรโตคอล () ปุ่ม ฯลฯ - จะรบกวนกิจกรรมนี้ (ข้อผิดพลาด "ขณะดำเนินการ") แทนที่จะยุติเพียงอย่างเดียว . ทางออกที่ดีที่สุดในเกือบทุกกรณีคือการใช้ธง นี่คือตัวอย่างวิธีการใช้งานที่เรียบง่ายและไร้สาระ (แม้ว่าฉันมั่นใจว่าพวกคุณส่วนใหญ่ไม่ต้องการมัน! :)
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()
สิ่งนี้จะยุติกิจกรรมกราฟิกได้อย่างดี คุณต้องตรวจสอบrunning
ในสถานที่ที่ถูกต้องเท่านั้น
ฉันขอขอบคุณคำตอบของ Apostolos ที่แจ้งให้ฉันทราบ นี่คือตัวอย่างรายละเอียดเพิ่มเติมสำหรับ Python 3 ในปี 2019 พร้อมคำอธิบายและโค้ดตัวอย่างที่ชัดเจนยิ่งขึ้น
destroy()
(หรือไม่มีตัวจัดการการปิดหน้าต่างแบบกำหนดเองเลย) จะทำลายหน้าต่างและการเรียกกลับที่ทำงานอยู่ทั้งหมดทันทีเมื่อผู้ใช้ปิดหน้าต่างสิ่งนี้อาจไม่ดีสำหรับคุณขึ้นอยู่กับกิจกรรม Tkinter ในปัจจุบันของคุณและโดยเฉพาะอย่างยิ่งเมื่อใช้tkinter.after
(การโทรกลับเป็นระยะ) คุณอาจใช้การโทรกลับซึ่งประมวลผลข้อมูลบางส่วนและเขียนลงดิสก์ ... ในกรณีนี้คุณต้องการให้การเขียนข้อมูลเสร็จสิ้นโดยไม่ถูกฆ่าอย่างกะทันหัน
ทางออกที่ดีที่สุดคือการใช้ธง ดังนั้นเมื่อผู้ใช้ร้องขอการปิดหน้าต่างให้คุณทำเครื่องหมายว่าเป็นแฟล็กจากนั้นตอบสนอง
(หมายเหตุ: ปกติแล้วฉันจะออกแบบ GUI ให้เป็นคลาสที่ห่อหุ้มอย่างดีและเธรดของผู้ปฏิบัติงานแยกกันและฉันไม่ได้ใช้ "global" (ฉันใช้ตัวแปรอินสแตนซ์คลาสแทน) แต่นี่เป็นตัวอย่างที่เรียบง่ายและถูกถอดออกเพื่อแสดงให้เห็น Tk ฆ่าการโทรกลับเป็นระยะ ๆ ของคุณได้อย่างไรเมื่อผู้ใช้ปิดหน้าต่าง ... )
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()
รหัสนี้จะแสดงให้คุณเห็นว่าWM_DELETE_WINDOW
ตัวจัดการทำงานแม้ในขณะที่ประเพณีของเราperiodic_call()
กำลังยุ่งอยู่ระหว่างทำงาน / วนซ้ำ!
เราใช้.after()
ค่าที่ค่อนข้างเกินจริง: 500 มิลลิวินาที นี่เป็นเพียงเพื่อให้คุณเห็นความแตกต่างระหว่างการปิดในขณะที่สายไม่ว่างหรือไม่ ... หากคุณปิดในขณะที่หมายเลขกำลังอัปเดตคุณจะเห็นว่าสิ่งที่WM_DELETE_WINDOW
เกิดขึ้นในขณะที่คุณโทรเป็นระยะ "คือ ไม่ว่างประมวลผล: จริง " หากคุณปิดในขณะที่หมายเลขหยุดชั่วคราว (หมายความว่าการโทรกลับเป็นระยะไม่ประมวลผลในขณะนั้น) คุณจะเห็นว่าการปิดเกิดขึ้นในขณะที่ "ไม่ว่าง"
ในการใช้งานจริงคุณ.after()
จะใช้บางอย่างเช่น 30-100 มิลลิวินาทีเพื่อให้มี GUI ที่ตอบสนอง นี่เป็นเพียงการสาธิตเพื่อช่วยให้คุณเข้าใจวิธีป้องกันตัวเองจากพฤติกรรม "ขัดจังหวะงานทั้งหมดทันทีเมื่อปิด" ของ Tk
โดยสรุป: กำหนดให้WM_DELETE_WINDOW
ตัวจัดการตั้งค่าสถานะจากนั้นตรวจสอบค่าสถานะนั้นเป็นระยะ ๆ และ.destroy()
หน้าต่างด้วยตนเองเมื่อปลอดภัย (เมื่อแอปของคุณทำงานทั้งหมดเสร็จแล้ว)
PS: คุณสามารถใช้WM_DELETE_WINDOW
เพื่อถามผู้ใช้ว่าพวกเขาต้องการปิดหน้าต่างจริงๆหรือไม่ และถ้าเขาตอบว่าไม่คุณก็ไม่ต้องตั้งค่าสถานะ มันง่ายมาก คุณเพียงแค่แสดงกล่องข้อความในของคุณWM_DELETE_WINDOW
และตั้งค่าสถานะตามคำตอบของผู้ใช้
ลองรุ่นง่าย ๆ :
import tkinter
window = Tk()
closebutton = Button(window, text='X', command=window.destroy)
closebutton.pack()
window.mainloop()
หรือหากคุณต้องการเพิ่มคำสั่งเพิ่มเติม:
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()