Необходимо сказать несколько слов о еще одном изменении в версии 2.1, прежде чем перейти к программному коду, поскольку он иллюстрирует основы закрытия окон tkinter в действующей программе. В главе 8 мы узнали, что библиотека tkinter позволяет выполнять с помощью метода bind привязку обработчика к событию <Destroy>, которое возбуждается при закрытии окна или уничтожении виджета. Мы могли бы привязать обработчики этого события к окнам PyEdit или к их текстовым виджетам, чтобы перехватить момент завершения программы, но это не принесло бы нам никакой выгоды в данной ситуации. В обработчике этого события сценарии вообще не могут выполнять какие-либо операции с графическим интерфейсом, потому что к моменту его вызова графический интерфейс уже разрушен. В частности, попытка проверить наличие изменений в текстовом виджете или извлечь его содержимое в обработчике события <Destroy> может привести к исключению. Вывод диалога с сообщением о необходимости сохранения также может действовать несколько странно: он появится только после того, как некоторые виджеты окна уже будут стерты (включая текстовый виджет, содержимое которого пользователь должен был бы проверить и сохранить!), а иногда даже вообще может не появится.
Кроме того, как уже упоминалось в главе 8, вызов метода quit не возбуждает события <Destroy>, но вызывает фатальную ошибку Python при выходе. Чтобы вообще иметь возможность использовать события <Destroy>, при выполнении операции Quit редактор PyEdit должен был бы закрывать окна только вызовом метода destroy и полагаться на протокол закрытия корневого окна Tk — непосредственное завершение приложения оказалось бы невозможным или потребовало бы использования таких инструментов, как sys.exit. Поскольку в обработчике события <Destroy> любые операции с графическим интерфейсом оказываются недопустимыми, использование этого приема ничем не оправдано. Программный код, выполняемый после вызова функции mainloop, также не способен помочь решить эту проблему, потому что функция mainloop вызывается за пределами PyEdit и после выхода из нее оказывается слишком поздно проверять наличие изменений и выполнять сохранения.
Иными словами, событие <Destroy> не решает проблему проверки необходимости сохранения перед закрытием окна, и оно никак не помогает в случае вызова методов quit и destroy виджетов из-за пределов классов окон PyEdit. Из-за этих сложностей PyEdit полагается на проверку изменений перед закрытием в каждом отдельном окне и проверяет наличие изменений в окнах, находящихся в списке, прежде чем закрыть любое из главных окон. Приложения, следующие данной модели окон, будут выполнять проверку автоматически. Приложения, использующие PyEdit как компонент более крупного графического интерфейса или использующие его иными способами, управляя редактором PyEdit извне, сами должны проверять наличие несохраненных изменений при закрытии, еще до того, как объект PyEdit или его виджеты будут разрушены.
Чтобы поэкспериментировать с событием <Destroy>, отыщите в дереве примеров файл destroyer.py — он имитирует действия, которые должен был бы выполнить редактор PyEdit при получении события <Destroy>. Ниже приводится наиболее важный фрагмент из этого сценария с комментариями, поясняющими его поведение:
def onDeleteRequest():
print(‘Got wm delete’) # щелчок на кнопке X в окне: можно отменить root.destroy() # возбудит событие <Destroy>
def doRootDestroy(event):
print(‘Got event <destroy>’) # для каждого виджета в корневом окне if event.widget == text:
print(‘for text’)
print(text.edit_modified()) # <= ошибка Tcl: неверный виджет
ans = askyesno(‘Save stuff?’, ‘Save?’) # <= некорректное поведение if ans: print(text.get(‘1.0’, END+’-1c’)) # <= ошибка Tcl: неверный # виджет
root = Tk()
text = Text(root, undo=1, autoseparators=1) text.pack()
root.bind(‘<Destroy>’, doRootDestroy) # для корневого и дочерних
root.protocol(‘WM_DELETE_WINDOW’, onDeleteRequest) # на кнопке X окна
Button(root, text=’Destroy’, command=root.destroy).pack() # возбудит <Destroy> Button(root, text=’Quit’, command=root.quit).pack() # <= фатальная ошибка mainloop() # Python, quit() не
# возбуждает <Destroy>
Дополнительные подробности, касающиеся всего, о чем говорилось выше, ищите в листингах, которые приводятся в следующем разделе. Кроме того, обязательно прочитайте строку документирования в главном файле, где приводится список предлагаемых расширений и решений проблемы открытия файлов (под заголовком «TBD»). На реализацию редактора PyEdit значительное влияние оказали влияние мои личные предпочтения, но вы можете настроить его под себя.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011