Я всегда наблюдаю за библиотеками для Pytho для работы с PDF и в один прекрасный день я столкнулся сpdfrw. Это похоже на аналогpyPDF, так как позволяет Вам читать и записывать PDFы, объединять их и использовать Reportlab для пометки их водяными знаками, кроме всего прочего. Проект немного мёртв, так как последнее обновление было в 2011 году, но с учётом того, что pyPDF обновлялся в 2010 — это не так уж и плохо. В этой статье мы проведём небольшой тест-драйв pdfrw и посмотрим, на что он годен. Поехали!
Заметка об установке: К сожалению у этой библиотеки нет скрипта setup.py, так что Вам придётся получить исходники с Google Code а затем скопировать папку pdfrw в site-packages или в ваш virtualenv.
Я думаю, у этого проекта большой потенциал. К счастью, мы можем проявить к нему достаточно интереса, чтобы снова запустит его, ну или найти что-то новенькое.
Я недавно выпустил версию 0.10 pylibftdi — «минимального питонного интерфейса для libftdi», предназначенного для наиболее лёгкого (из всех возможных?) способов работы с чипами и модулями FTDI имитирующими последовательный порт при подключении к usb. Версия 0.10 добавляет несколько новых возможностей и исправляет несколько ошибок.
Долгое время я был в плену заблуждения, что номера версий должны соответствовать правилам десятичной системы, и что после версии 0.9 должна идти версия 1.0. Но, поскольку, я хочу, чтобы версия 1.0 была стабильной (а нынешний статус я всё ещё рассматриваю как «бета»), я попал в тупик. В итоге я решил, что новая версия будет иметь номер 0.10. В любом случае, я всё ещё хочу улучшить документацию (и не только) перед выпуском 1.0.
Изменения в 0.10
Запуск юнит-тестов теперь проще благодаря некоторым реорганизациям — просто выполните python -m unittest discover в папке верхнего уровня
Поддержка устройств FT232H — это другой PID (USB product ID) для тех устройств, которые я раньше использовал — в основном FT245BM/RL, FT232R/RL. Все эти устройства имели PID 0x6001, тогда как новый FT232H имел PID 0x6014. Я экспериментировал с дополнительными параметрами для определения VID и PID целевого устройства, но это оказалось слишком сложно для конечного пользователя, а я бы хотел, чтобы pylibftdi можно было бы использовать с параметрами по умолчанию для большинства основных операций. В итоге я взял два списка (USB_VID_LIST,USB_PID_LIST) и код поиска устройства должен просто провести итерации по их декартову произведению (то есть вложенный цикл, реализованный через itertools.product). Так что добавление новых PID в будущем будет простым — надо лишь добавить его в USB_VID_LIST, и тогда устройство может быть открыто без параметров конструктором Device() если на USB шине есть только FTDI устройство.
Устройство при открытии устанавливается в параллельный режим. В рассылке libftdi был спор о том, как эту логику реализовать, но в pylibftdi это сделать достаточно легко. Вообще это позволяет избежать неопределённого состояния, так как раньше, если предыдущее приложение, которое работало с устройством в режиме BitBang, то новое открытие устройства при помощи Device() отрывало его тоже в режиме BitBang, а не в ожидаемом параллельном режиме (для устройств, которые поддерживают оба режима).
В конструктор Device() добавлен параметр 'buffer_size' (по умолчанию равен 0, что соответствует поведению раньше), который разбивает на куски операции чтения и записи. Это позволяет избежать проблемы, когда вызов (например) dev.write ('hello' * 100000) при передаче 9600 занимал невероятно много времени, а так как он протекал в контексте библиотеки (через вызов ctypes), его нельзя было прервать при помощи Ctrl-C
Убрана устаревшая возможность использовать Driver() как синоним Device()
Обновлено: Я уже сделал два дополнительных релиза в последние часы — текущая версия — 0.10.2. Одним из значимых изменений является то, что теперь субпакет exmaples включён в sdist, так что python -m pylibftdi.examples.led_flash будет работать, если у Вас диод подключён к D0 на устройстве.
Планы на будущее: причесать, добавить примеры, улучшить документацию…
создавать модальные диалоговые окна и окна с сообщениями
использовать стандартные диалоги
создавать помощников
показывать советы при запуске
создавать и использовать валидаторы
В то время как фреймы используются для продолжительных взаимодействий с пользователем, диалоги обычно нужны для того, чтобы получить некоторое количество информации от пользователя и передать её на обработку дальше. Зачастую диалоговые окна открываются в модальном режиме, что не позволяет другим фреймам перехватывать события, пока диалог не закрыт. В это главе мы обсудим виды диалогов, доступных в wxPython, хотя кроме них Вы можете создать и свои собственные варианты. Среди предустановленных диалогов есть как простые, только для вывода информации, так и более сложные, например, для открытия файлов.
9.1 Работа с модальными диалогами
Модальные диалоги используются или для того, чтобы быстро вывести / получить какую-либо информацию, либо когда без новых данных от пользователя невозможно двигаться дальше. wxPython содержит несколько функций для отображения таких диалогов: диалог с сообщением, со строкой для ввода и с выбором из списка. В следующем разделе мы посмотрим, как эти диалоги выглядят и как Вы можете облегчить себе жизнь с помощью имеющихся функций.
9.1.1 Как создать модальный диалог
Модальный диалог не позволяет другим виджетам получать события от пользователя до тех пор, пока диалог не будет закрыт. Как видно из рисунка 9.1, по внешнему виду не всегда можно отличить, является ли открытое окно диалогом или фреймом.
В этом листинге надо отметить несколько деталей: при инициализации кнопка добавляется непосредственно к диалогу, а не на панель. Вообще, панели гораздо реже используются в диалогах, чем во фреймах. Отчасти потому, что диалоги должны быть как можно проще, но главная причина в том, что функционал панели (стандартный фон и переключение между элементами с помощью табуляции) уже реализован в диалогах.
Для отображения диалога как модального окна используется метод ShowModal(), имеющийся от метода Show(), который используется для отображения фреймов. Ваше приложение останавливается в точке вызова ShowModal() до тех пор, пока диалоговое окно не будет закрыто. В этот момент единственным, кто может взаимодействовать с пользователем является диалог (понятно, что другие приложения продолжают работать в обычном режиме).
Программно диалог можно закрыть с помощью метода EndModal(retCode), где retCode — числовое значение, которое возвращается методом ShowModal(). Обычно оно используется для того, чтобы сообщить приложению каким образом было закрыто диалоговое окно и на основании этого принять соответствующее решение. При этом важно помнить, что прекращение отображения диалога ещё не значит его уничтожения — даже после этого он продолжает существовать, благодаря чему после его закрытия Вы всё ещё можете получить данные, введённые пользователем. Пример этого мы увидим в следующем разделе.
Возможно, Вы заметили, что в листинге не было определено ни одного обработчика, что должно было заставить Вас задуматься: «Почему же тогда диалог реагирует на нажатие кнопок?» Никакого секрета тут нет — это поведение уже определено в wx.Dialog. Есть два предопределённых ID, которые имеют особое значение для диалогов:
когда нажимается wx.Button с ID равным wx.ID_OK, диалог закрывается и в точку вызова ShowModal() возвращается значение wx.ID_OK
аналогично действует нажатие на кнопку со значением wx.ID_CANCEL, возвращающая соответствующее значение
после чего приложению остаётся лишь корректно обработать значения кнопок.
Листинг 9.1 показывает стандартный способ работы с модальными диалогами. После активации диалога значение, которое он возвращает, используется в качестве условия в выражении if. В нашем случае мы всего лишь выводим на экран выбор пользователя. Однако обычно в случае результата wx.ID_OK предпринимаются более сложные действия, основанные на выборе пользователя, такие как открытие файла или выбор цвета.
Обычно Вы должны явным образом уничтожить диалог когда Вы заканчиваете его использовать, так как после этого он может быть утилизован сборщиком мусора Python. Если же Вы хотите повторно использовать диалог без того, чтобы тратить время на его создание (например, если это какой-то сложный экземпляр), то Вы можете сохранить на него ссылку и отобразить его позже, вызвав метод ShowModal(). Но при этом важно помнить, что перед завершением программы диалог всё равно придется уничтожить, так как иначе MainLoop() будет рассматривать его как окно верхнего уровня и не сможет корректно завершить работу.
9.1.2 Как вывести пользователю сообщение?
Три наиболее простых диалога для взаимодействия с пользователем — это wx.MessageDialog, выводящий сообщение, wx.TextEntryDialog, предлагающий пользователю ввести короткий текст и wx.SingleChoiceDialog, позволяющий пользователю выбрать варианты из предоставленного списка. В следующих трёх секциях мы обсудим эти три диалога.
Диалог сообщения, как следует из названия, отображает короткое сообщение и закрывается по нажатию кнопки. Обычно он используется для вывода предупреждений или получения ответа на вопрос да / нет. На рисунке 9.2 отображён такой диалог.
рис 9.2
Использование такого диалога не вызовет у Вас затруднений: в листинге 9.2 представлено два способа создания такого диалога.
Листинг 9.2
importwx if__name__
=="__main__": app=wx.PySimpleApp() # с использованием классов dlg=wx.MessageDialog(None,"Is this explanation OK?", 'A Message Box',wx.YES_NO|wx.ICON_QUESTION) retCode=dlg.ShowModal() if(retCode==wx.ID_YES): print"yes" else: print"no" dlg.Destroy() # с использованием функции retCode=wx.MessageBox("Is this way easier?","Via Function", wx.YES_NO|wx.ICON_QUESTION)
Мы создаём два диалоговых окна, одно за другим. В первый раз мы создаём экземпляр класса wx.MessageDialog, после чего вызываем его метод ShowModal(). Используем класс wx.MessageDialog
Используя конструктор класса wx.MessageDialog Вы можете задать сообщение в диалоге и выбрать для него необходимые кнопки:
message — текст, который будет отображаться в диалоге; если Вы хотите разделить его на несколько строк — используйте 'n'. caption отображается в заголовке диалога. pos позволяет Вам определить место на экране, где будет отображён диалог, однако Windows этот аргумент игнорирует.
Флаги стиля можно разделить на две группы. Первая содержит кнопки, которые будут отображаться в диалоге:
Стиль
Описание
wx.CANCEL
Добавляет кнопку «отменить» с идентификатором wx.ID_CANCEL
wx.NO_DEFAULT
Если установлен флаг wx.YES_NO, то кнопкой по умолчанию будет «нет»
wx.OK
Добавляет кнопку «да» с идентификатором wx.ID_OK
wx.YES_DEFAULT
Если установлен флаг wx.YES_NO, то кнопкой по умолчанию будет «да». Такая настройка применяется по умолчанию
wx.YES_NO
Добавляет кнопки «да» и «нет» со значениями wx.ID_OK и wx.ID_NO соответственно
Вторая группа флагов отвечает за знак, который будет отображён рядом с текстом:
Стиль
Описание
wx.ICON_ERROR
Знак ошибки
wx.ICON_EXCLAMATION
Восклицательный знак
wx.ICON_HAND
То же, что и wx.ICON_ERROR
wx.ICON_INFORMATION
Знак информационного сообщения (буква i)
wx.ICON_QUESTION
Вопросительный знак
И наконец, Вы можете использовать флаг стиля wx.STAY_ON_TOP, чтобы отобразить сообщение поверх всех окон в системе.
Как можно видеть из листинга, для того, чтобы отобразить диалог, вызывается метод ShowModal(). В зависимости от отображаемых кнопок Вы получите в ответ wx.ID_OK, wx.ID_CANCEL, wx.ID_YES или wx.ID_NO. Как обычно, результат используется для управления дальнейшей работой программы.
Использование функции wx.MessageBox()
В листинге 9.2 показан и более короткий способ отображения информационного окна: специальная функция wx.MessageBox() сама создаёт диалог, вызывает его метод ShowModall() и возвращает одно из четырёх значений: wx.ID_OK, wx.ID_CANCEL, wx.ID_YES или wx.ID_NO. Сам вызов функции проще конструктора wx.MessageDialog: wx.MessageBox(message,caption=»Message»,style=wx.OK) В этом примере message,caption и style имеют те же значения, что и в конструкторе класса и Вы можете использовать те же самые флаги стиля. Как Вы увидите дальше в этой главе, многие из предустановленных диалогов имеют схожие функции для их быстрого вызова. Пока Вы планируете использовать диалог всего один раз, можете спокойно выбирать любой из этих двух способов. Но если Вы хотите сохранить диалог в памяти для дальнейшего использования — лучше создать собственный экземпляр. Однако для таких простых диалогов, как наш, экономии времени Вы, конечно, не достигните.
Для того, чтобы отобразить более объемны
й текст (например, лицензию) Вы можете использовать доступный только для Python класс wx.lib.dialogs.ScrolledMessageDialog, использующий такой конструктор: wx.lib.dialogs.ScrolledMessageDialog(parent, msg, caption, pos=wx.wxDefaultPosition, size=(500,300)) Этот класс не использует встроенные диалоги, а конструирует свой из доступных виджетов. Он отображает только кнопку «ОК» и не принимает флагов стиля.
9.1.3 Как получить короткий текст от пользователя?
Второй простой тип диалога — wx.TextEntryDialog, используемый для получения ввода от пользователя небольшого объема информации. Обычно Вы встречаетесь с ним, когда от Вас требуют имя пользователя или пароль при запуске программы. На рисунке 9.3 отображён типичный представитель этого класса.
рис 9.3
Код, вызвавший его к жизни, приведён в листинге 9.3
Листинг9.3
importwx if__name__=="__main__": app=wx.PySimpleApp() dialog=wx.TextEntryDialog(None, "What kind of text would you like to enter?", "Text Entry","Default Value",style=wx.OK|wx.CANCEL) ifdialog.ShowModal()==wx.ID_OK: print"You entered: %s"%dialog.GetValue() dialog.Destroy()
Как и в предыдущем разделе, мы создаём экземпляр класса диалога, в данном случае wx.TextEntryDialog, однако конструктор этого класса чуть более сложный, чем предыдущий:wx.TextEntryDialog(parent, message, caption=»Please enter text», defaultValue=»», style=wx.OK | wx.CANCEL | wx.CENTRE, pos=wx.DefaultPosition) message — текст приглашения к вводу, caption — текст, отображаемый в заголовке окна. defaultValue, если задано, отображает значение, появляющееся в поле для ввода при отображении диалога. Флаги стиля могут содержать wx.OK и wx.CANCEL для отображения соответствующих кнопок.
В данном диалоге можно использовать и некоторые флаги стиля виджета wx.TextCtrl. Наиболее полезные из них: wx.TE_PASSWORD для замены символов звёздочками, wx.TE_MULTILINE, позволяющий вводить несколько строк текста, и wx.TE_LEFT, wx.TE_CENTRE с wx.TE_RIGHT, определяющие выравнивание вводимого текста.
Последняя строка из этого листинга иллюстрирует ещё одно отличие от информационного диалога: введённая пользователем информация сохраняется в экземпляре диалога и может быть использована после его закрытия. В нашем примере мы получаем её с помощью метода GetValue(). Не стоит забывать, что если пользователь нажал на «отмену», это значит, что он не хочет использовать введённое значение, поэтому имеет смысл проводить проверку, какая кнопка была нажата. Кроме того, Вы можете ввести значение программно с помощью метода SetValue().
Кроме того, у нас есть ещё три удобных функции для этих целей:
wx.GetTextFromUser()
wx.GetPasswordFromUser()
wx.GetNumberFromUser()
Наиболее похожим образом на наш пример действует функция wx.GetTextFromUser(): wx.GetTextFromUser(message, caption=»Input text», default_value=»», parent=None) Все значения в этом примере идентичны конструктору класса. Если пользователь нажимает «ок», то функция возвращает введёный текст, иначе Вы получите пустую строку. Для получения пароля надо использовать функцию wx.GetPasswordFromUser(): wx.GetPasswordFromUser(message, caption=»Input text», default_value=»», parent=None) И тут с аргументами у Вас не должно возникнуть проблем. Вводимый текст, как понятно, заменяется звёздочками. Возвращаемые значения аналогичны предыдущей фу
Практически все операционные системы содержат свои стандартные диалоги для таких повседневных задач, как выбор файла, шрифта или цвета. Это позволяет придерживаться во всех приложениях некоего стандартного внешнего вида и облегчает пользователю работу с вашей программой. Благодаря wxPython Вы тоже можете воспользоваться этим преимуществом; более того, Вы сможете использовать эти диалоги даже на тех платформах, где они не предоставляются операционной системой.
9.2.1 Диалог выбора файла
Диалог выбора файлов обычно стандартный для всех приложений. Класс wx.FileDialog использует родные диалоги для тех систем, где они предусмотрены и свой собственный вид там, где их нет. Версия такого диалога для Windows изображена на рисунке 9.6
рис 9.6
Вы можете указать каталог по умолчанию и маски файлов для отображения, как это сделано в листинге 9.6:
Листинг 9.6
importwx importos if__name__=="__main__": app=wx.PySimpleApp() wildcard="Python source (*.py)|*.py|" "Compiled Python (*.pyc)|*.pyc|" "All files (*.*)|*.*" dialog=wx.FileDialog(None,"Choose a file",os.getcwd(), "",wildcard,wx.OPEN) ifdialog.ShowModal()==wx.ID_OK: printdialog.GetPath() dialog.Destroy()
Диалог выбора файла является одним из наиболее сложных, так как имеет несколько свойств, доступных для чтения / записи. Часть этих свойств Вы можете установить при создании экземпляра этого класса: wx.FileDialog(parent, message=»Choose a file», defaultDir=»», defaultFile=»», wildcard=»*.*», style=0, pos=wx.DefaultPosition) message содержит заголовок диалога, defaultDir указывает на каталог, который будет отображён при открытии диалога. Если он не указан или установлен на несуществующий каталог, то будет показано содержимое рабочего каталога. defaultFile содержит название файла и обычно используется при сохранении файлов. wildcart устанавливает фильтры файлов, доступных для выбора, причём в нем можно использовать шаблоны * и ?. В аргументе может содержаться как один шаблон, например *.py, так и набор шаблонов в таком формате: <описание> | <шаблон> | <описание> | <шаблон>, как в листинге выше: «Python source (*.py)|*.py|Compiled Python (*.pyc)|*.pyc| All files (*.*)|*.*» Если Вы используете набор шаблонов, то они будут доступны через выпадающий список, как на рисунке 9.6. И ещё, нет никакой гарантии, что аргумент pos будет учитываться конкретной ОС.
Выбор файла
Два наиболее важных флага стиля для данного диалога — это wx.Open и wx.Save, которые определяют поведение диалогового окна. Диалог, используемый для открытия файла может содержать ещё два флага стиля, а именно wx.HIDE_READONLY, не позволяющий открыть файл в режиме «только для чтения», и wx.MULTIPLE, который позвол
яет выбрать несколько файлов в одной директории. Для диалога сохранения есть один полезный флаг — wx.OVERWRITE_PROMT, который требует подтверждения от пользователя при его попытке перезаписать уже существующий файл. Другие типы диалога могут использовать флаг wx.CHANGE_DIR. При его наличии выбор файла изменяет директорию по умолчанию для этого диалога на директорию, содержащую этот файл. Так что в следующий раз открытый диалог уже будет находиться в той директории и для этого Вам не надо сохранять где-либо её название. В отличие от других диалогов, рассмотренных выше, все эти свойства доступны для чтения и записи при помощи соответствующих методов. Это касается свойств directory, filename, style, message и wildcard, для который существуют соответствующие Get- и Set- методы. После того, как пользователь закрыл диалог и Вы убедились, что он сделал это кнопкой «ОК», Вы можете получить выбранный им файл с помощью метода GetPath(), возвращающего полное имя и путь к файлу в виде строки. Если Вы использовали флаг wx.MULTIPLE, то Вам потребуется метод GetPaths(), который вернёт Вам список из строк. Если же по какой-то причине Вам надо знать, какой фильтр при этом использовался, то метод GetFilterIndex() вернёт Вам индекс этого фильтра. Для программного изменения выбранного фильтра используйте метод SetFilterIndex(). Кроме того, Вы можете использовать следующую функцию вместо создания экземпляра класса: wx.FileSelector(message, default_path=»», default_filename=»», default_extension=»», wildcard=»*.*'', flags=0, parent=None, x=-1, y=-1) Назначение почти всех аргументов понятно из названия, кроме flags — обычно он называется style и default_extension, указывающий на расширение, которое будет добавлено к имени файла при его сохранении. Если пользователь нажимает на «ОК», Вы получите путь к файлу, иначе Вам достанется лишь пустая строка.
Выбор папки
Если Вам надо предоставить пользователю возможность выбора не файла а папки, используйте диалог wx.DirDialog, отображающий древовидную структуру папок, как показано на рисунке 9.7 Вызов этого диалога несколько проще, чем диалога для выбора файла, как видно из листинга 9.7:
рис 9.7
Листинг 9.7
importwx if__name__=="__main__": app=wx.PySimpleApp() dialog=wx.DirDialog(None,"Choose a directory:", style=wx.DD_DEFAULT_STYLE|wx.DD_NEW_DIR_BUTTON) ifdialog.ShowModal()==wx.ID_OK: printdialog.GetPath() dialog.Destroy()
Практически вся функциональность этого диалога заключена в конструкторе: wx.DirDialog(parent, message=»Choose a directory», defaultPath=»», style=0, pos = wx.DefaultPosition, size = wx.DefaultSize, name=»wxDirCtrl») Поскольку message отображается в самом диалоге, Вам нет необходимости (да по всей видимости и возможности — прим. переводчика) в изменении заголовка диалога. defaultPath указывает выбор по умолчанию, пустая строка в данном случае будет означать корень файловой системы. pos и size игнорируются в Windows, а name игнорируется во всех ОС. Флаг стиля wx.DD_NEW_DIR_BUTTON добавляет кнопку создания новой папки, причём он может не работать в старых версиях Winows. Свойства path, message и style имеют стандартный методы получения и установки. После закрытия диалога можно получит выбранное значение методом GetPath(). Кроме того, вызвать диалог можно и с помощью функции: wx.DirSelector(message=wx.DirSelectorPromptStr, default_path=»», style=0, pos=wxDefaultPosition, parent=None) Аргументы аналогичны конструктору класса. Функция возвращает путь к папке в виде строки или пустую строку, если была нажата кнопка отмены.
9.2.2 Диалог выбора шрифта
Диалог выбора шрифта отличается от диалога выбора файла, та
Мастер — это набор простых диалогов, соединённых последовательно друг с другом. Их цель — провести пользователя через какую-либо процедуру путём подачи и получения информации небольшими частями. На рисунке 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() не будет вызван и обработка события на этом завершается. Если у Вас возникнет необходимость, Вы можете связать и другие методы