Обертывание интерфейсов окон верхнего уровня

obertyvanie interfejsov okon verhnego urovnya Приемы программирования графических интерфейсов

Интерфейсы окон верхнего уровня были представлены в главе 8. Данный раздел продолжает обсуждение с того места, где это представление завершилось, и демонстрирует обертывание этих интерфейсов классами, автоматизирующими большую часть операций по созданию окон верхнего уровня, — установку заголовков, поиск и отображение ярлыков окон, выполнение действий по закрытию окна исходя из его назначения, перехват события щелчка мышью на кнопке закрытия окна в его заголовке и так далее.

В примере 10.16 приводятся определения классов-оберток для наиболее часто используемых типов окон — главного окна приложения, временного всплывающего окна и окна встроенного компонента графического интерфейса. Эти типы окон несколько отличаются друг от друга реализациями операции закрытия, но наследуют множество общих черт: ярлыки, заголовки и кнопки закрытия. Используя непосредственно, подмешивая или наследуя класс окна требуемого типа, вы бесплатно получаете в свое распоряжение все логику настройки.

Пример 10.16. PP4E\Gui\Tools\windows.py

############################################################################## Классы, инкапсулирующие интерфейсы верхнего уровня.

Позволяют создавать главные, всплывающие или присоединяемые окна; эти классы могут наследоваться непосредственно, смешиваться с другими классами или вызываться непосредственно, без создания подклассов; должны подмешиваться после (то есть правее) более конкретных прикладных классов: иначе подклассы будут получать методы (destroy, okayToQuit) из этих, а не из прикладных классов, и лишатся возможности переопределить их.

##############################################################################

import os, glob

from tkinter import Tk, Toplevel, Frame, YES, BOTH, RIDGE from tkinter.messagebox import showinfo, askyesno

class _window:

подмешиваемый класс, используется классами главных и всплывающих окон

foundicon = None # совместно используется всеми экземплярами

iconpatt = ‘*.ico # может быть сброшен

iconmine = py.ico

def configBorders(self, app, kind, iconfile):

if not iconfile: # ярлык не был передан?

iconfile = self.findIcon() # поиск в тек. каталоге и в каталоге

title = app # модуля

if kind: title += ‘ — ‘ + kind self.title(title) # на рамке окна

self.iconname(app) # при свертывании

if iconfile:

try:

self.iconbitmap(iconfile) # изображение ярлыка окна

except: # проблема с интерпретатором или

pass # платформой

self.protocol(‘WM_DELETE_WINDOW’, self.quit) # не закрывать без

# подтверждения

def findIcon(self): if _window.foundicon: # ярлык уже найден?

return _window.foundicon iconfile = None # сначала искать в тек. каталоге

iconshere = glob.glob(self.iconpatt) # допускается только один if iconshere: # удалить ярлык с красными

iconfile = iconshere[0] # буквами Tk

else: # поиск в каталоге модуля

mymod = __import__(__name__) # импортировать, получить каталог

path = __name__.split(‘.’) # возможно, путь пакета

for mod in path[1:]: # по всему пути до конца

mymod = getattr(mymod, mod) # только самый первый

mydir = os.path.dirname(mymod.__file__)

myicon = os.path.join(mydir, self.iconmine) # исп. myicon, а не tk if os.path.exists(myicon): iconfile = myicon

_window.foundicon = iconfile # не выполнять поиск вторично

return iconfile

class MainWindow(Tk, _window):

главное окно верхнего уровня

def __init__(self, app, kind=’’, iconfile=None):

Tk.__init__(self)

self.__app = app

self.configBorders(app, kind, iconfile)

def quit(self):

if self.okayToQuit(): # потоки запущены?

if askyesno(self.__app, ‘Verify Quit Program?’): self.destroy() # завершить приложение

else:

showinfo(self.__app, ‘Quit not allowed’) # или в okayToQuit?

def destroy(self): # просто завершить

Tk.quit(self) # переопределить, если необходимо

def okayToQuit(self): # переопределить, если используются

return True # потоки выполнения

class PopupWindow(Toplevel, _window):

вторичное всплывающее окно

def __init__(self, app, kind=’’, iconfile=None):

Toplevel.__init__(self)

self.__app = app

self.configBorders(app, kind, iconfile)

def quit(self): # переопределить, если потребуется изменить

if askyesno(self.__app, ‘Verify Quit Window?’): # или вызвать destroy self.destroy() # чтобы закрыть окно

def destroy(self): # просто закрыть окно

Toplevel.destroy(self) # переопределить, если необходимо

class QuietPopupWindow(PopupWindow):

def quit(self):

self.destroy() # закрывать без предупреждения

class ComponentWindow(Frame):

при присоединении к другим интерфейсам

def __init__(self, parent): # если не фрейм

Frame.__init__(self, parent) # предоставить контейнер self.pack(expand=YES, fill=BOTH)

self.config(relief=RIDGE, border=2) # перенастроить при необходимости

def quit(self):

showinfo(‘Quit’, ‘Not supported in attachment mode’)

# destroy из фрейма: просто удалить фрейм # переопределить, если

# необходимо

Почему бы просто не определять ярлык приложения и заголовок окна непосредственно вызовом методов? С одной стороны, особенности такого рода нелегко запомнить (в результате вам придется большую часть времени тратить на копирование и вставку кода). С другой стороны, эти классы добавляют высокоуровневые функциональные возможности, реализацию которых иначе пришлось бы добавлять снова и снова. Кроме всего прочего, эти классы обеспечивают автоматический запрос подтверждения завершения и поиск ярлыка. Например, классы окон всего один раз пытаются отыскать файл ярлыка в текущем рабочем каталоге и в каталоге, где находится данный модуль.

Используя классы, инкапсулирующие — то есть скрывающие — такие подробности, мы получаем всю мощь инструмента, без необходимости задумываться об их реализации в будущем. Кроме того, с помощью этих классов мы можем придавать нашим приложениям стандартизованный внешний вид и поведение. А если в будущем их потребуется изменить, нам достаточно будет изменить программный код только в одном месте, а не во всех реализованных нами окнах.

Для тестирования этого модуля в примере 10.17 приводится сценарий, использующий эти классы в различных режимах — в качестве подмешиваемых классов, в качестве суперклассов и непосредственно — из обычного процедурного программного кода.

Пример 10.17. PP4E\Gui\Tools\windows-test.py

#  модуль windows должен импортироваться, иначе атрибут __name__ будет иметь

#  значение __main__ в функции findIcon

from tkinter import Button, mainloop

from windows import MainWindow, PopupWindow, ComponentWindow

def _selftest():

#  использовать, как подмешиваемый класс class content:

“используется так же, как Tk, Toplevel и Framedef __init__(self):

Button(self, text=’Larch’, command=self.quit).pack()

Button(self, text=’Sing ‘, command=self.destroy).pack()

class contentmix(MainWindow, content): def __init__(self):

MainWindow.__init__(self, ‘mixin’, ‘Main’) content.__init__(self) contentmix()

class contentmix(PopupWindow, content): def __init__(self):

PopupWindow.__init__(self, ‘mixin’, ‘Popup’) content.__init__(self) prev = contentmix()

class contentmix(ComponentWindow, content):

def __init__(self): # вложенный фрейм

ComponentWindow.__init__(self, prev) # в предыдущем окне content.__init__(self) # кнопка Sing стирает фрейм

contentmix()

#  использовать в подклассах class contentsub(PopupWindow): def __init__(self):

PopupWindow.__init__(self, ‘popup’, ‘subclass’)

Button(self, text=’Pine’, command=self.quit).pack()

Button(self, text=’Sing’, command=self.destroy).pack() contentsub()

#  использование в процедурном программном коде win = PopupWindow(‘popup’, ‘attachment’) Button(win, text=’Redwood’, command=win.quit).pack() Button(win, text=’Sing ‘, command=win.destroy).pack() mainloop()

if __name__ == ‘__main__’: _selftest()

Если запустить этот тест, он создаст четыре окна, как показано на рис. 10.10. Все окна автоматически получат ярлык с голубыми буквами «PY» и будут перехватывать и запрашивать подтверждение при попытке закрыть их щелчком на кнопке X в правом верхнем углу, благодаря логике поиска и настройки, унаследованной из классов окон в модуле. Некоторые кнопки в окнах, воспроизводимых тестовым сценарием, закрывают только вмещающее их окно, некоторые — все приложение, некоторые стирают только присоединенное окно, а некоторые выводят диалог с просьбой подтвердить закрытие окна. Запустите этот сценарий у себя на компьютере, чтобы увидеть, как действуют различные кнопки, и получить возможность сопоставить их поведение с реализацией в сценарии — действия, выполняемые при закрытии окна, зависят от его типа.

Рис. 10.10. Интерфейс сценария windowstest

 

Мы будем использовать эти классы-обертки в следующей главе, в примере PyClock, и еще раз — в главе 14, где они будут использоваться, чтобы уменьшить сложность программы PyMailGUI. Отчасти преимущество применения приемов ООП в языке Python состоит в том, что благодаря им мы можем позднее не вспоминать детали реализации.

Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011

Оцените статью
Секреты программирования
Добавить комментарий