Як я обробляю подію закриття вікон у 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 (наприклад: скручений реакторний об'єкт), переконайтеся, що зовнішня головна петля зупинена будь-якими смітиками, які передбачені для цієї мети (наприклад: reactor.stop () для скручених)
Брайан Джек

4
У моєму Python 2.7 в Windows Tkinterне було скриньки підмодулів. Я використавimport tkMessageBox as messagebox
IronManMark20,

Я думаю, вам слід було б знати, що ви скопіювали цю відповідь та код від когось / де ще.
Крістіан Дін

1
Я не знаю, це не той код, який я спочатку розмістив.
Метт Григорій

Не працює для мене. Це не змінює хаотичну реакцію класичного Пітона на переривання графіки, коли важко закрити вікно (наприклад, з 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()

У цьому прикладі ми надаємо користувачеві два нові варіанти виходу:
класичний Файл → Вихід, а також Escкнопку.


12

Залежно від активності 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сталося під час періодичного дзвінка " зайнята обробка: правда ". Якщо ви закриваєтесь, поки номери призупинені (це означає, що періодичний зворотний виклик не обробляється на той момент), ви бачите, що закриття відбулося, поки воно не "зайняте".

У реальному використанні, ви .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()

Питання стосується кнопки X ОС для закриття вікна, а не звичайного управління кнопками.
користувач1318499

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.