Мастер — это набор простых диалогов, соединённых последовательно друг с другом. Их цель — провести пользователя через какую-либо процедуру путём подачи и получения информации небольшими частями. На рисунке 9.11 изображён пример такого простого мастера, содержащего кнопки «вперед» и «назад».
рис 9.11
В wxPython мастер представляет из себя набор страниц, управляемый экземпляром класса wx.wizard.Wizard. Этот экземпляр обрабатывает события, создаваемые пользователем. Сами же страницы представляют из себя экземпляры классов wx.wizard.WizardPageSimple или wx.wizard.WizardPage. В обоих случаях это всего лишь экземпляры класса wx.Panel с дополнительной логикой для обслуживания последовательности страниц. Разница же между этими двумя классами в том, что wx.wizard.WizardPage позволяет динамически определить, к какой странице следует совершить переход, тогда как для wx.wizard.WizardPageSimple последовательность должна быть задана ещё на момент создания мастера. Листинг 9.11 показывает код создания простого мастера:
Листинг 9.11
import wx import wx.wizard # создаём простую страницу class TitledPage(wx.wizard.WizardPageSimple): def __init__(self, parent, title): wx.wizard.WizardPageSimple.__init__(self, parent) self.sizer = wx.BoxSizer(wx.VERTICAL) self.SetSizer(self.sizer) titleText = wx.StaticText(self, -1, title) titleText.SetFont( wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)) self.sizer.Add(titleText, 0, wx.ALIGN_CENTRE | wx.ALL, 5) self.sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.ALL, 5) if __name__ == "__main__": app = wx.PySimpleApp() # создаём экземпляр мастера wizard = wx.wizard.Wizard(None, -1, "Simple Wizard") # создаём страницы мастера page1 = TitledPage(wizard, "Page 1") page2 = TitledPage(wizard, "Page 2") page3 = TitledPage(wizard, "Page 3") page4 = TitledPage(wizard, "Page 4") page1.sizer.Add(wx.StaticText(page1, -1, "Testing the wizard")) page4.sizer.Add(wx.StaticText(page4, -1, "This is the last page.")) # задаём последовательность страниц wx.wizard.WizardPageSimple_Chain(page1, page2) wx.wizard.WizardPageSimple_Chain(page2, page3) wx.wizard.WizardPageSimple_Chain(page3, page4) # задаём размер мастера wizard.FitToPage(page1) # запускаем мастера if wizard.RunWizard(page1): print "Success"
Для наполнения мастера мы создаём страницы, которые содержат только фиксированный текст. Вы, скорее всего, захотите добавить сюда ещё какие-то элементы, например, для получения данных от пользователя.
Функция wx.wizard.WizardPageSimple_Chain() является более простым способом построения последовательности, чем вызов методов SetNext() и SetPrev() страниц, переданных ей в качестве аргументов.
Вызов FitToSize() устанавливает размер мастера на основании страницы, переданной ему в качестве аргумента, и всех страниц, доступных от неё по цепочке. Поэтому, вызывайте этот метод лишь после того, как установите последовательность страниц.
Аргументом метода wizard.RunWizard() служит страница, с которой начинается запуск мастера. Причём мастер сам понимает, что надо завершиться, когда он доходит до страницы, для которой не установлена следующая страница. Значение True возвращается только если пользователь прошёл все страницы и нажал в конце «Завершить».
Создание экземпляра wx.wizard.Wizard — первый шаг в использовании мастера. Конструктор класса выглядит так: wx.wizard.Wizard(parent, id=-1, title=wx.EmptyString, bitmap=wx.NullBitmap, pos=wx.DefaultPosition) В нашем примере parent, id, title и pos имеют тот же смысл, что и в конструкторе wx.Panel. Если передан аргумент bitmap, то он будет отображаться на каждой странице. Из доступных флагов стиля есть только wx.WIZARD_EX_HELPBUTTON, который отображает кнопку помощи. Это флаг расширенного стиля и его надо устанавливать используя двухступенчатый механизм, описанный в главе 8. Обычно, для установки размера Вы будете использовать FitToSize(), как в нашем примере, но ничто не мешает Вам задать размер самому с помощью метода SetPageSize(), передав ему в качестве аргумента кортеж или экземпляр wx.Size. Для получения текущего размера используется метод GetPageSize(). В любом случае размер будет использоваться лишь для той части мастера, которая предназначена для отображения страниц, сам мастер будет большего размера (например, из-за кнопок «далее» и «назад» — прим. переводчика). Контролировать работу мастера можно с помощью его же методов: метод GetCurrentPage() возвращает отображаемую страницу, если же мастер ещё не запущен он вернёт None. Определить, имеет ли текущая стра
ница следующую или предыдущую можно с помощью методов HasNextPage() и HasPrevPage(). Для запуска мастера используется метод RunWizard(), как видно из нашего листинга. Мастер продуцирует командные события, которые Вы сами можете перехватывать и обрабатывать; они перечислены в таблице ниже. Каждое из этих событий относится к классу wx.wizard.WizardEvent, для которого доступно два метода. GetPage() вернёт нам страницу, на которой было создано событие, а GetDirection() вернёт True, если пользователь движется вперёд по мастеру, и False в другом случае.
Событие
Описание
EVT_WIZARD_CANCEL
Создаётся, когда пользователь нажимает кнопку «отмена». Это событие может быть отменено с помощью метода Veto(). В таком случае диалог не завершит работу.
EVT_WIZARD_FINISHED
Создаётся, когда пользователь нажимает кнопку «готово» в конце мастера.
EVT_WIZARD_HELP
Создаётся, когда пользователь нажимает кнопку справки
EVT_WIZARD_PAGE_CHANGED
Создаётся после смены страницы для постобработки
EVT_WIZARD_PAGE_CHANGING
Создаётся когда пользователь нажал на кнопку изменения страницы, но страница ещё не изменилась. Это событие так же может быть отменено с помощью Veto(), например, если пользователь заполнил не все поля.
Класс wx.wizard.WizardPageSimple обрабатывается так же как панель. Конструктор этого класса позволяет Вам сразу установить предыдущую и следующую страницу: wx.wizard.WizardPageSimple(parent=None, prev=None, next=None) Опять же, если Вы не хотите задавать их при создании — используйте методы SetPrev() и SetNext(). Если же и с этим у Вас возникают проблемы — Вам поможет функция wx.wizard.WizardPageSimple_Chain(), которая устанавливает взаимосвязь между двумя страницами. Более сложная версия страниц мастера, wx.wizard.WizardPage, немногим отличается от рассмотренного нами. Но вместо того, чтобы явно устанавливать последовательность страниц, она предоставляет методы, позволяющие определить более сложную логику следования по мастеру. Конструктор класса выглядит так: wx.WizardPage(parent, bitmap=wx.NullBitmap, resource=None) Заданный аргумент bitmap переопределяет свой аналог в конструкторе мастера. resource позволяет загрузить страницу из ресурсов wxPython. Для реализации логики следования переопределите методы GetPrev() и GetNext(), чтобы они возвращали то, что надо делать дальше. Обычно это используется в случаях, когда последовательность страниц определяется ответами пользователя.
9.4 Отображение подсказок при запуске
Многие программы используют такие подсказки чтобы сообщить пользователям о тех возможностях, о которых иначе можно было бы и не узнать. wxPython предоставляет простой способ создания таких подсказок. Пример и код такого окна приведён на рисунке 9.12 и листинге 9.12 соответственно.
У нас есть две функции, управляющие такими советами. Первая создаёт wx.TipProvider: wx.CreateFileTipProvider(filename, currentTip) filename — имя файла, содержащего эти советы. currentTip — индекс отображаемого совета (нумерация идёт с 0). Приложение само должно хранить эту информацию между запусками. Файл с советами — это простой текстовый файл, где каждый совет занимает ровно одну строчку. Пустые линии пропускаются, так же как и линии, начинающиеся с #. Вот пример содержания такого файла: Создавать советы очень легко. Feel the force, Luke. Поставщик советов (tip provider) является экземпляром класса wx.PyTipProvider. Если Вам нужно больше функциональности, Вы можете определить свой подкласс класса wx.TipProvider и переопределить его метод GetTip(). За отображение советов ответственна функция wx.ShowTip(). wx.ShowTip(parent, tipProvider, showAtStartup) parent — родительское окно, при наличии, а tipProvider — это то, что мы создали на предыдущем шаге. showAtStartup отвечает за то, стоит ли галочка в чекбоксе «показывать при старте», но при этом она не следит, отказался ли пользователь от них — это Вы должны проверить сами. Функция возвращает значение этого чекбокса, так что Вы можете использовать его при следующем запуске вашего приложения, если, конечно, сохраните это значение.
Валидатор — это такой специальный объект wxPython, который облегчает работу с данными в диалогах. Мы уже говорили о них в третьей главе, когда упомянули, что валидатор может быть автоматически вызван системным событием. Более того, мы уже видели, что они передаются в качестве параметров некоторым классам, но так до сих пор и не обсудили что же это такое. Валидатор выполняет три различные функции:
Проверяет данные в элементах перед закрытием диалогов
Автоматически передаёт данные из / в диалог
Проверяет данные по мере их ввода пользователем
9.5.1 Как с помощью валидатора убедиться, что я получил верные данные?
Валидатор представляет из себя подкласс класса wx.Validator, который в свою очередь является абстрактным классом в наборе wxWidget для С++, поэтому Вам необходимо определять свои собственные классы на его основе. Как мы увидим позже, Ваши классы создаются не напрямую от wx.Validator, а посредством наследования эксклюзивного класса wx.PyValidator, что даёт Вам возможность переопределять все родительские методы (среди них надо переопределить метод Clone(), возвращающий копию валидатора). Сам валидатор должен быть прикреплён к какому-нибудь виджету, что можно сделать двумя способами. Во-первых, если виджет это позволяет, валидатор может быть передан его конструктору в качестве аргумента. В противном случае Вы можете создать виджет и прикрепить к нему валидатор с помощью метода этого виджета SetValidator(validator). Для того, чтобы осуществлять проверку вводимых данных надо сперва переопределить метод Validate(parent) в вашем подклассе валидаторов. Аргумент parent — это родительское окно вашего виджета, соответственно это либо диалог либо панель. Вы можете использовать его для получения информации с других виджетов или просто проигнорировать. Для получения ссылки на связаный с вашим валидатором виджет используйте метод self.GetWindow(). Значение, возвращаемое методом Validate() имеет логический тип: True означает, что данные введены верно, False — что есть какая-то ошибка. Вы можете использовать wx.MessageBox() для отображения предупреждения в методе Validate(), но Вы не должны делать что-либо, что приводит к созданию новых событий. Значение, возвращаемое методом Validate() играет роль когда пользователь закрывает диалог, нажимая на кнопку «ОК». Как часть обработки этого события wxPthon вызывает функции Validate() всех виджетов диалога, если эти функции определены. И если хоть одна из этих функций вернёт False, диалог не закроется. Листинг 9.13 показывает простой диалог с валидатором, который проверяет наличие данных во всех текстовых полях:
Листинг 9.13
import wx about_txt = """ Валидатор, используемый в этом примере проверяет наличие текста в текстовых полях при нажатии на кнопку "ок" и не даёт Вам закрыть диалог, если это условие не выполняется."""
# создаём подкласс валидатора class NotEmptyValidator(wx.PyValidator): def __init__(self): wx.PyValidator.__init__(self)
def Clone(self): """ Обратите внимание, что каждый валидатор должен реализовать метод Clone(). """ return NotEmptyValidator()
# метод проверки def Validate(self, win): textCtrl = self.GetWindow() text = textCtrl.GetValue()
if len(text) == 0: wx.MessageBox("This field must contain some text!", "Error") textCtrl.SetBackgroundColour("pink") textCtrl.SetFocus() textCtrl.Refresh() return False else: textCtrl.SetBackgroundColour( wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) textCtrl.Refresh() return True
def TransferToWindow(self): return True
def TransferFromWindow(self): return True
class MyDialog(wx.Dialog): def __init__(self): wx.Dialog.__init__(self, None, -1, "Validators: validating")
# Создаём поля для ввода текста about = wx.StaticText(self, -1, about_txt) name_l = wx.StaticText(self, -1, "Name:") email_l = wx.StaticText(self, -1, "Email:") phone_l = wx.StaticText(self, -1, "Phone:")
Метод Validate() проверяет, что связанные с ним виджеты содержат какой-нибудь текст. Если же это не так, то цвет их фона меняется на розовый
Для использования валидатора мы прикрепляем его к виджету, передавая в качестве аргумента конструктора виджета экземпляр нашего класса
На рисунке 9.13 показан наш диалог после попытки закрыть его с одним пустым полем.
рис 9.13
Код, который заставляет диалог задействовать валидаторы отсутствует в листинге, так как он является частью системы событий wxPython. В этом заключается ещё одна разница между фреймами и диалогами — диалоги автоматически запускают валидацию, а фреймы нет. Если же Вам понадобиться использовать валидацию во фреймах, то вызовите метод Validate() родительского окна. В таком случае, если у Вас установлен расширеный флаг стиля wx.WS_EX_VALIDATE_RECURSIVELY, будут вызваны методы валидации и всех дочерних виджетов. Как и в случае с диалогом, если хотя бы одна валидация будет провалена, проверка выдаст False. В следующем разделе мы поговорим о том, как с помощью валидаторов передвать данные.
9.5.2 Передача данных с помощью валидаторов
Вторая важная функция валидаторов состоит в том, что они автоматически передают данные в диалог перед его отображением и из диалога при его закрытии. На рисунке 9.14 изображён простейший диалог.
рис 9.14
Для того, чтобы реализовать эту функциональность Вам потребуется переопределить ещё два метода вашего подкласса. Метод TransferToWindow() вызывается автоматически при открытии диалога; его необходимо использовать для передачи данных в виджет, связаный с валидатором. Метод TransferFromWindow() вызывается при закрытии диалога кнопкой «ок» уже после выполненных проверок. Его необходимо использовать для передачи данных из виджета в другое место. Тот факт, что данные откуда-то и куда-то передаются, означает, что валидатор должен знать об этих откуда-то и куда-то, как показано в листинге 9.14. В этом примере каждый валидатор инициализируется со ссылкой на глобальный словарь данных и с ключом этого словаря. Когда диалог открывается TransferToWindow() читает значение словаря для данного ключа и помещает данные в текстовое поле. При закрытии диалога метод TransferFromWindow() делает то же самое для записи значения в этот словарь. В примере будет отображено окно диалога для того, чтобы показать Вам процесс передачи данных.
Листинг 9.14
import wx import pprint
about_txt = """ В данном примере валидатор используется для демонстрации возможности автоматической передачи данных в и из текстового поля при открытии и закрытии диалога."""
# объявляем валидатор class DataXferValidator(wx.PyValidator): def __init__(self, data, key): wx.PyValidator.__init__(self) self.data = data self.key = key
def Clone(self): """ Будьте внимательны: каждый валидатор должен реализовать метод Clone() """ return DataXferValidator(self.data, self.key)
# не проверяем данные def Validate(self, win): return True
wx.MessageBox("You entered these values:nn" + pprint.pformat(data)) app.MainLoop()
Вызов методов передачи данных валидаторов происходит в диалогах автоматически. Для того же, чтобы использовать их вне пределов диалогов вызывайте методы TransferDataFromWindow() и TransferDataToWindow() родительского окна. В таком случае, если у Вас установлен расширеный флаг стиля wx.WS_EX_VALIDATE_RECURSIVELY, будут вызваны методы передачи данных и всех дочерних виджетов. В следующем разделе мы поговорим о наиболее востребованном использовании валидаторов — проверке данных в процессе их ввода пользователем.
9.5.3 Как проверять данные в процессе ввода?
Кроме всего прочего Вы можете использовать валидаторы для проверки данных, вводимых пользователем, «на лету», ещё до их передачи в виджет. Это очень важная возможность, так как Вы можете уберечь свою программу от попадания в неё плохих данных. На рисунке 9.15 изображён пример такого диалога с текстом, поясняющим эту идею.
рис 9.15
Данный метод проверки данных менее автоматизирован чем те, что мы рассмотрели выше. Вы должны напрямую связать событие введения символов в виджет валидатора с функцией проверки: self.Bind(wx.EVT_CHAR, self.OnChar) Виджет сам передаёт событие валидатору. Пример реализации такой возможности приведён в листинге 9.15
Листинг 9.15
import wx import string
about_txt = """ Валидатор, используемый в данном примере проверяет "на лету" ввод данных пользователем и не ждёт нажатия кнопки "ок". Первое поле не допускает ввода цифр, во второе поле можно ввести всё, что угодно, а в третье - всё, кроме букв. """
class CharValidator(wx.PyValidator): def __init__(self, flag): wx.PyValidator.__init__(self) self.flag = flag # связываем функцию с событием ввода символа self.Bind(wx.EVT_CHAR, self.OnChar)
def Clone(self): """ Будьте внимательны: каждый валидатор должен реализовать метод Clone() """ return CharValidator(self.flag)
def Validate(self, win): return True
def TransferToWindow(self): return True
def TransferFromWindow(self): return True
# обработчик ввода данных в виджет def OnChar(self, evt): key = chr(evt.GetKeyCode()) if self.flag == "no-alpha" and key in string.letters: return if self.flag == "no-digit" and key in string.digits: return evt.Skip()
class MyDialog(wx.Dialog): def __init__(self): wx.Dialog.__init__(self, None, -1, "Validators: behavior modification")
# Создаём поля для ввода текста about = wx.StaticText(self, -1, about_txt) name_l = wx.StaticText(self, -1, "Name:") email_l = wx.StaticText(self, -1, "Email:") phone_l = wx.StaticText(self, -1, "Phone:")
Так как метод OnChar() находится в валидаторе, он будет вызван ещё до реакции виджета на ввод символа. Мы так же позволяем виджету обработать этот метод, вызывая метод события Skip() (иначе это повлияло бы на нормальную обработку этого события). Валидатор производит проверку допустимости ввода данных и если ввод содержит недопустимые символы метод Skip() не будет вызван и обработка события на этом завершается. Если у Вас возникнет необходимость, Вы можете связать и другие методы
Как добавлять подменю, всплывающие меню и собственные виды меню
Как создавать удобные меню
Трудно представить себе приложение без знакомой полоски сверху со словами «Файл», «Редактировать» и «Справка». Меню настолько привычная часть интерфейса, что многие не уделяют достаточного внимания его созданию, что тоже плохо. Ведь настолько легко и быстро предоставить пользователю доступ к возможностям вашего приложения без меню практически не возможно!
У вас наверняка в летнем гардеробе есть цветной сарафан и стильная летняя сумка в тон к нему. Вот было бы здорово, если бы и обувь подходила к этому наряду идеально. Сегодня будем изготовлять шлепанцы на свой вкус, идеально подходящие к нашему наряду. Красивая обувь из вьетнамок своими руками — что может быть проще?
Открытый огонь завораживает и притягивает к себе человека. Неслучайно самые приятные беседы происходят у вечернего костра в туристических походах. Но не всегда есть возможность отправиться в поход с ночевкой. Наверное, поэтому человек придумал уличный камин, построив его в своем саду. Садовый камин представляет собой громоздкое сооружение, требующее немалых затрат на его возведение. Проще всего соорудить в саду компактный очаг, где у открытого огня можно наслаждаться красотой звездного неба и ощутить романтику дикой природы.
Хотя садовый очаг у нас не так популярен, как, например, в США, все же, я думаю, мода на него придет и к нам. Рассмотрим строительство садового очага своими руками в пошаговом исполнении, где автор подробно показывает все этапы его сооружения.