Как мне обработать событие закрытия окна (пользователь нажимает кнопку «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()
В этом примере мы даем пользователю два новых варианта выхода:
классический File → Exit, а также 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
произошло, когда ваш периодический вызов "был занятая обработка: True ". Если вы закроете, когда номера приостановлены (это означает, что периодический обратный вызов в этот момент не обрабатывается), вы увидите, что закрытие произошло, пока он «не занят».
В реальной жизни вам .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()