Как мне обработать событие закрытия окна в Tkinter?

131

Как мне обработать событие закрытия окна (пользователь нажимает кнопку «X») в программе Python Tkinter?

Мэтт Грегори
источник

Ответы:

178

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()
Мэтт Грегори
источник
2
Если вы используете что-то вроде Twisted, которое поддерживает цикл событий независимо, или Tkinter (например, объект реактора twisted), убедитесь, что внешний основной цикл остановлен с помощью любых smenatics, которые он предоставляет для этой цели (например, response.stop () для twisted)
Брайан Джек
4
На моем Python 2.7 в Windows Tkinterне было окна сообщений подмодуля. Я использовалimport tkMessageBox as messagebox
IronManMark20
Я думаю, вам следует сообщить, что вы скопировали этот ответ и код от кого-то / где еще.
Кристиан Дин
1
Я не знаю, это не тот код, который я изначально опубликовал.
Мэтт Грегори
У меня не работает. Это не меняет хаотическую реакцию классического Python на прерывание графики при жестком закрытии окна (например, с помощью Alt + F4).
Апостолос
29

Мэтт показал одну классическую модификацию кнопки закрытия.
Другой - сделать так, чтобы кнопка закрытия сворачивала окно.
Вы можете воспроизвести это поведение, имея окно программы сразу способом
быть в протокольном втором аргументе метода.

Вот рабочий пример, протестированный в 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кнопку.

Честный Эйб
источник
14

В зависимости от активности 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в нужном месте (ах).

Апостолоса
источник
4

Я хотел бы поблагодарить Апостола за ответ, который обратил на это мое внимание. Вот гораздо более подробный пример для 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и устанавливаете флаг на основе ответа пользователя.

Митч МакМаберс
источник
1

Попробуйте простую версию:

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()
Исследование SF12
источник
Вопрос касается кнопки X ОС для закрытия окна, а не обычного кнопочного элемента управления.
user1318499
-1
from tkinter import*
root=Tk()
exit_button=Button(root,text="X",command=root.quit)
root.mainloop()
Тирт Ананд
источник