Як я обробляю подію закриття вікна (користувач натискає кнопку '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
Метт показав одну класичну модифікацію кнопки закриття.
Інша полягає в тому, щоб кнопка закриття мінімізувала вікно.
Ви можете відтворити це поведінка, маючи вікно програми відразу способом
бути в протокольному другому аргументі методу.
Ось робочий приклад, протестований на 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.after, зупинення цієї діяльності 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
в потрібному місці.
Я хотів би подякувати відповідь Апостолоса за те, що він звернув на це увагу. Ось набагато докладніший приклад для Python 3 в 2019 році, з більш чітким описом та прикладом коду.
destroy()
(або взагалі не має спеціального обробника вікон закриття) знищить вікно та всі його запущені зворотні виклики миттєво, коли користувач його закриє.Це може бути погано для вас, залежно від вашої поточної активності Tkinter, і особливо при використанні tkinter.after
(періодичні зворотні виклики). Можливо, ви використовуєте зворотний дзвінок, який обробляє деякі дані та записує на диск ... у цьому випадку ви, очевидно, хочете, щоб запис даних закінчувався, не будучи різко вбитим.
Найкращим рішенням для цього є використання прапора. Отже, коли користувач просить закрити вікно, ви позначаєте це як прапор, а потім реагуєте на нього.
(Примітка. Я зазвичай проектую графічні інтерфейси як добре інкапсульовані класи та окремі робочі потоки, і я точно не використовую "глобальний" (замість цього використовую змінні екземпляри класу), але це мається на увазі простий прибитий приклад для демонстрації як 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 мілісекунд, щоб мати чуйний графічний інтерфейс. Це лише демонстрація, яка допоможе вам зрозуміти, як захистити себе від типового поведінки 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()