В этой главе рассказывается о том, как:
- создавать модальные диалоговые окна и окна с сообщениями
- использовать стандартные диалоги
- создавать помощников
- показывать советы при запуске
- создавать и использовать валидаторы
9.1 Работа с модальными диалогами
9.1.1 Как создать модальный диалог
import wx
class SubclassDialog(wx.Dialog):
def __init__(self): #инициируем диалог
wx.Dialog.__init__(self, None, -1, 'Dialog Subclass',size=(300, 100))
okButton = wx.Button(self, wx.ID_OK, "OK", pos=(15, 15))
okButton.SetDefault()
cancelButton = wx.Button(self, wx.ID_CANCEL, "Cancel",pos=(115, 15))
if __name__ == '__main__':
app = wx.PySimpleApp()
dialog = SubclassDialog()
result = dialog.ShowModal() #показываем модальный диалог
if result == wx.ID_OK:
print "OK"
else:
print "Cancel"
dialog.Destroy()
- когда нажимается wx.Button с ID равным wx.ID_OK, диалог закрывается и в точку вызова ShowModal() возвращается значение wx.ID_OK
- аналогично действует нажатие на кнопку со значением wx.ID_CANCEL, возвращающая соответствующее значение
после чего приложению остаётся лишь корректно обработать значения кнопок.
9.1.2 Как вывести пользователю сообщение?
Листинг 9.2
import wx
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
| Стиль | Описание |
|---|---|
| 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.MessageBox()
В этом примере message,caption и style имеют те же значения, что и в конструкторе класса и Вы можете использовать те же самые флаги стиля. Как Вы увидите дальше в этой главе, многие из предустановленных диалогов имеют схожие функции для их быстрого вызова. Пока Вы планируете использовать диалог всего один раз, можете спокойно выбирать любой из этих двух способов. Но если Вы хотите сохранить диалог в памяти для дальнейшего использования — лучше создать собственный экземпляр. Однако для таких простых диалогов, как наш, экономии времени Вы, конечно, не достигните.
й текст (например, лицензию) Вы можете использовать доступный только для Python класс wx.lib.dialogs.ScrolledMessageDialog, использующий такой конструктор:
wx.lib.dialogs.ScrolledMessageDialog(parent, msg, caption, pos=wx.wxDefaultPosition, size=(500,300))
Этот класс не использует встроенные диалоги, а конструирует свой из доступных виджетов. Он отображает только кнопку «ОК» и не принимает флагов стиля.
9.1.3 Как получить короткий текст от пользователя?
Листинг9.3
import wx
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)
if dialog.ShowModal() == wx.ID_OK:
print "You entered: %s" % dialog.GetValue()
dialog.Destroy()
- wx.GetTextFromUser()
- wx.GetPasswordFromUser()
- wx.GetNumberFromUser()
wx.GetTextFromUser(message, caption=»Input text», default_value=»», parent=None)
Все значения в этом примере идентичны конструктору класса. Если пользователь нажимает «ок», то функция возвращает введёный текст, иначе Вы получите пустую строку.
Для получения пароля надо использовать функцию wx.GetPasswordFromUser():
wx.GetPasswordFromUser(message, caption=»Input text», default_value=»», parent=None)
И тут с аргументами у Вас не должно возникнуть проблем. Вводимый текст, как понятно, заменяется звёздочками. Возвращаемые значения аналогичны предыдущей фу
wxPython in Action. Глава 9. Возможность выбора. Диалоги. (часть 2)
9.2 Использование стандартных диалогов
9.2.1 Диалог выбора файла
Листинг 9.6
import wx
import os
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)
if dialog.ShowModal() == wx.ID_OK:
print dialog.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.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, указывающий на расширение, которое будет добавлено к имени файла при его сохранении. Если пользователь нажимает на «ОК», Вы получите путь к файлу, иначе Вам достанется лишь пустая строка.
Выбор папки
Вызов этого диалога несколько проще, чем диалога для выбора файла, как видно из листинга 9.7:
Листинг 9.7
import wx
if __name__ == "__main__":
app = wx.PySimpleApp()
dialog = wx.DirDialog(None, "Choose a directory:",
style=wx.DD_DEFAULT_STYLE | wx.DD_NEW_DIR_BUTTON)
if dialog.ShowModal() == wx.ID_OK:
print dialog.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 Диалог выбора шрифта
wxPython in Action. Глава 9. Возможность выбора. Диалоги. (часть 3)
9.3 Создание мастеров
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(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 Отображение подсказок при запуске
Листинг 9.12
import wx
if __name__ == "__main__":
app = wx.PySimpleApp()
provider = wx.CreateFileTipProvider("tips.txt", 0)
wx.ShowTip(None, provider, True)
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 отвечает за то, стоит ли галочка в чекбоксе «показывать при старте», но при этом она не следит, отказался ли пользователь от них — это Вы должны проверить сами. Функция возвращает значение этого чекбокса, так что Вы можете использовать его при следующем запуске вашего приложения, если, конечно, сохраните это значение.
Автор: Ishayahu Lastov
wxPython in Action. Глава 9. Возможность выбора. Диалоги. (часть 4)
9.5 Использование валидаторов
Валидатор выполняет три различные функции:
9.5.1 Как с помощью валидатора убедиться, что я получил верные данные?
Сам валидатор должен быть прикреплён к какому-нибудь виджету, что можно сделать двумя способами. Во-первых, если виджет это позволяет, валидатор может быть передан его конструктору в качестве аргумента. В противном случае Вы можете создать виджет и прикрепить к нему валидатор с помощью метода этого виджета SetValidator(validator).
Для того, чтобы осуществлять проверку вводимых данных надо сперва переопределить метод Validate(parent) в вашем подклассе валидаторов. Аргумент parent — это родительское окно вашего виджета, соответственно это либо диалог либо панель. Вы можете использовать его для получения информации с других виджетов или просто проигнорировать. Для получения ссылки на связаный с вашим валидатором виджет используйте метод self.GetWindow(). Значение, возвращаемое методом Validate() имеет логический тип: True означает, что данные введены верно, False — что есть какая-то ошибка. Вы можете использовать wx.MessageBox() для отображения предупреждения в методе Validate(), но Вы не должны делать что-либо, что приводит к созданию новых событий.
Значение, возвращаемое методом Validate() играет роль когда пользователь закрывает диалог, нажимая на кнопку «ОК». Как часть обработки этого события wxPthon вызывает функции Validate() всех виджетов диалога, если эти функции определены. И если хоть одна из этих функций вернёт False, диалог не закроется. Листинг 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:")
# используем валидаторы
name_t = wx.TextCtrl(self, validator=NotEmptyValidator())
email_t = wx.TextCtrl(self, validator=NotEmptyValidator())
phone_t = wx.TextCtrl(self, validator=NotEmptyValidator())
# Используем стандартные ID кнопок
okay = wx.Button(self, wx.ID_OK)
okay.SetDefault()
cancel = wx.Button(self, wx.ID_CANCEL)
# размещаем виджеты с помощью координаторов
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(about, 0, wx.ALL, 5)
sizer.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.ALL, 5)
fgs = wx.FlexGridSizer(3, 2, 5, 5)
fgs.Add(name_l, 0, wx.ALIGN_RIGHT)
fgs.Add(name_t, 0, wx.EXPAND)
fgs.Add(email_l, 0, wx.ALIGN_RIGHT)
fgs.Add(email_t, 0, wx.EXPAND)
fgs.Add(phone_l, 0, wx.ALIGN_RIGHT)
fgs.Add(phone_t, 0, wx.EXPAND)
fgs.AddGrowableCol(1)
sizer.Add(fgs, 0, wx.EXPAND|wx.ALL, 5)
btns = wx.StdDialogButtonSizer()
btns.AddButton(okay)
btns.AddButton(cancel)
btns.Realize()
sizer.Add(btns, 0, wx.EXPAND|wx.ALL, 5)
self.SetSizer(sizer)
sizer.Fit(self)
app = wx.PySimpleApp()
dlg = MyDialog()
dlg.ShowModal()
dlg.Destroy()
app.MainLoop()
- Метод Validate() проверяет, что связанные с ним виджеты содержат какой-нибудь текст. Если же это не так, то цвет их фона меняется на розовый
- Для использования валидатора мы прикрепляем его к виджету, передавая в качестве аргумента конструктора виджета экземпляр нашего класса
9.5.2 Передача данных с помощью валидаторов
Тот факт, что данные откуда-то и куда-то передаются, означает, что валидатор должен знать об этих откуда-то и куда-то, как показано в листинге 9.14. В этом примере каждый валидатор инициализируется со ссылкой на глобальный словарь данных и с ключом этого словаря. Когда диалог открывается TransferToWindow() читает значение словаря для данного ключа и помещает данные в текстовое поле. При закрытии диалога метод TransferFromWindow() делает то же самое для записи значения в этот словарь. В примере будет отображено окно диалога для того, чтобы показать Вам процесс передачи данных.
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
# вызывается при открытии диалога
def TransferToWindow(self):
textCtrl = self.GetWindow()
textCtrl.SetValue(self.data.get(self.key, ""))
return True
# вызывается при закрытии диалога
def TransferFromWindow(self):
textCtrl = self.GetWindow()
self.data[self.key] = textCtrl.GetValue()
return True
class MyDialog(wx.Dialog):
def __init__(self, data):
wx.Dialog.__init__(self, None, -1, "Validators: data transfer")
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:")
name_t = wx.TextCtrl(self,
validator=DataXferValidator(data, "name"))
email_t = wx.TextCtrl(self,
validator=DataXferValidator(data, "email"))
phone_t = wx.TextCtrl(self,
validator=DataXferValidator(data, "phone"))
okay = wx.Button(self, wx.ID_OK)
okay.SetDefault()
cancel = wx.Button(self, wx.ID_CANCEL)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(about, 0, wx.ALL, 5)
sizer.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.ALL, 5)
fgs = wx.FlexGridSizer(3, 2, 5, 5)
fgs.Add(name_l, 0, wx.ALIGN_RIGHT)
fgs.Add(name_t, 0, wx.EXPAND)
fgs.Add(email_l, 0, wx.ALIGN_RIGHT)
fgs.Add(email_t, 0, wx.EXPAND)
fgs.Add(phone_l, 0, wx.ALIGN_RIGHT)
fgs.Add(phone_t, 0, wx.EXPAND)
fgs.AddGrowableCol(1)
sizer.Add(fgs, 0, wx.EXPAND|wx.ALL, 5)
btns = wx.StdDialogButtonSizer()
btns.AddButton(okay)
btns.AddButton(cancel)
btns.Realize()
sizer.Add(btns, 0, wx.EXPAND|wx.ALL, 5)
self.SetSizer(sizer)
sizer.Fit(self)
app = wx.PySimpleApp()
data = { "name" : "Jordyn Dunn" }
dlg = MyDialog(data)
dlg.ShowModal()
dlg.Destroy()
wx.MessageBox("You entered these values:nn" +
pprint.pformat(data))
app.MainLoop()
В следующем разделе мы поговорим о наиболее востребованном использовании валидаторов — проверке данных в процессе их ввода пользователем.
9.5.3 Как проверять данные в процессе ввода?
self.Bind(wx.EVT_CHAR, self.OnChar)
Виджет сам передаёт событие валидатору. Пример реализации такой возможности приведён в листинге 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:")
# добавляем валидатор
name_t = wx.TextCtrl(self, validator=CharValidator("no-digit"))
email_t = wx.TextCtrl(self, validator=CharValidator("any"))
phone_t = wx.TextCtrl(self, validator=CharValidator("no-alpha"))
okay = wx.Button(self, wx.ID_OK)
okay.SetDefault()
cancel = wx.Button(self, wx.ID_CANCEL)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(about, 0, wx.ALL, 5)
sizer.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.ALL, 5)
fgs = wx.FlexGridSizer(3, 2, 5, 5)
fgs.Add(name_l, 0, wx.ALIGN_RIGHT)
fgs.Add(name_t, 0, wx.EXPAND)
fgs.Add(email_l, 0, wx.ALIGN_RIGHT)
fgs.Add(email_t, 0, wx.EXPAND)
fgs.Add(phone_l, 0, wx.ALIGN_RIGHT)
fgs.Add(phone_t, 0, wx.EXPAND)
fgs.AddGrowableCol(1)
sizer.Add(fgs, 0, wx.EXPAND|wx.ALL, 5)
btns = wx.StdDialogButtonSizer()
btns.AddButton(okay)
btns.AddButton(cancel)
btns.Realize()
sizer.Add(btns, 0, wx.EXPAND|wx.ALL, 5)
self.SetSizer(sizer)
sizer.Fit(self)
app = wx.PySimpleApp()
dlg = MyDialog()
dlg.ShowModal()
dlg.Destroy()
app.MainLoop()
wxPython in Action. Глава 10. Создаём и используем меню в wxPython.
В этой главе Вы узнаете:
- Как создавать меню
- Как работать с элементами меню
- Как добавлять подменю, всплывающие меню и собственные виды меню
- Как создавать удобные меню
10.1 Создаём меню
- Создать полосу меню (menu bar)
- Добавить полосу меню к фрейму
- Создать отдельные меню
- Добавить эти меню к полосе меню или к родительскому меню
- Создать отдельные пункты меню
- Добавить эти пункты к соответствующим меню
- Создать привязку (binding) для событий каждого пункта меню
10.1.1 Как создать полосу меню и прикрепить её к фрейму.
10.1.2 Как создать меню и прикрепить его к полосе меню?
wx.Menu(title=»», style=0)
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "Simple Menu Example")
p = wx.Panel(self)
# создаём панель меню
menuBar = wx.MenuBar()
# создаём меню
menu = wx.Menu()
# добавляем меню к панели меню
menuBar.Append(menu, "Left Menu")
menu2 = wx.Menu()
menuBar.Append(menu2, "Middle Menu")
menu3 = wx.Menu()
menuBar.Append(menu3, "Right Menu")
self.SetMenuBar(menuBar)
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show()
app.MainLoop()| Функция | Описание |
|---|---|
| Append(menu, title) | Добавляет menu в конец полосы меню (то есть в после последнего правого элемента). Параметр title используется для отображения нового меню. При удачном выполнении возвращает True, иначе — False. |
Insert(pos, menu, title) | Вставляет переданное menu в заданную в pos позицию (после данного вызова выражение GetMenu(pos)==menu будет истинно). Как и при вставке в список, все позиции всех последующих меню сдвигаются вправо. Отсчёт позиций начинается с 0, так что pos=0 эквивалентно помещению меню в крайнюю левую позицию полосы меню. Если в качестве pos передать результат вызова GetMenuCount(), то результат будет такой же как при вызове метода Append(). Параметр title используется для отображения нового меню. При удачном выполнении возвращает True, иначе — False. |
Remove(pos) | Удаляет меню из позиции pos, сдвигая оставшиеся меню влево. Возвращает то меню, которое было удалено. |
Replace(pos, menu, title) | Заменяет меню на позиции pos меню переданным в аргументе menu а для отображения использует параметр title. Не затрагивает остальные меню в полосе. Возвращает то меню, которое раньше находилось на этом месте. |
| Метод | Описание |
|---|---|
| EnableTop(pos, enable) | Делает меню на позиции pos доступным или нет, в зависимости от значения параметра enable — True или False. |
| GetMenu(pos) | Возвращает объект меню, находящийся в заданной позиции. |
| GetMenuCount() | Возвращает количество меню в полосе меню. |
| FindMenu(title) | Возвращает числовой индекс меню в полосе меню при заданном title. Если такого меню нет, метод возвращает wx.NOT_FOUND. Метод игнорирует декораторы акселераторов (то есть значки перед буквой для быстрого вызова) при их наличии. |
| GetLabelTop(pos) SetLabelTop(pos, label) | Метод установки и получения метки меню в данной позиции |
10.1.3 Как добавить элементы в выпадающее меню?
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1,
"Menu Example with StatusBar")
p = wx.Panel(self)
self.CreateStatusBar()
menu = wx.Menu()
simple = menu.Append(-1, "Simple menu item",
"This is some help text")
menu.AppendSeparator()
exit = menu.Append(-1, "Exit",
"Selecting this item will exit the program")
self.Bind(wx.EVT_MENU, self.OnSimple, simple)
self.Bind(wx.EVT_MENU, self.OnExit, exit)
menuBar = wx.MenuBar()
menuBar.Append(menu, "Menu")
self
.SetMenuBar(menuBar)
def OnSimple(self, event):
wx.MessageBox("You selected the simple menu item")
def OnExit(self, event):
self.Close()
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show()
app.MainLoop()
- Prepend(id, string, helpStr=»», kind=wx.ITEM_NORMAL
- PrependSeparator()
- Insert(pos, id, string, helpStr=»», kind=wx.ITEM_NORMAL)
- InsertSeparator(pos)
- AppendItem(aMenuItem)
- InsertItem(pos, aMenuItem)
- PrependItem(aMenuItem)
Все они ведут себя так, как и ожидается из их названия.
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1,
"Add Menu Items")
p = wx.Panel(self)
self.txt = wx.TextCtrl(p, -1, "new item")
btn = wx.Button(p, -1, "Add Menu Item")
# связываем метод с событием кнопки
self.Bind(wx.EVT_BUTTON, self.OnAddItem, btn)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self.txt, 0, wx.ALL, 20)
sizer.
Add(btn, 0, wx.TOP|wx.RIGHT, 20)
p.SetSizer(sizer)
self.menu = menu = wx.Menu()
simple = menu.Append(-1, "Simple menu item")
menu.AppendSeparator()
exit = menu.Append(-1, "Exit")
self.Bind(wx.EVT_MENU, self.OnSimple, simple)
self.Bind(wx.EVT_MENU, self.OnExit, exit)
menuBar = wx.MenuBar()
menuBar.Append(menu, "Menu")
self.SetMenuBar(menuBar)
def OnSimple(self, event):
wx.MessageBox("You selected the simple menu item")
def OnExit(self, event):
self.Close()
def OnAddItem(self, event):
# добавляем элемент меню
item = self.menu.Append(-1, self.txt.GetValue())
# связываем его с методом
self.Bind(wx.EVT_MENU, self.OnNewItemSelected, item)
def OnNewItemSelected(self, event):
wx.MessageBox("You selected a new item")
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show()
app.MainLoop()
10.1.4 Как отвечать на события меню?
| Тип события | Описание |
|---|---|
| EVT_MENU_CLOSE | Возникает когда меню закрывается |
| EVT_MENU_HIGHLIGHT | Возникает при подсветке (наведении на) элемента меню. Связано с конкретным id. По умолчанию отображает подсказку в статусной панели фрейма |
| EVT_MENU_HIGHLIGHT_ALL | Возникает когда меню подсвечивается на при этом не привязано к конкретному id, другими словами может быть только один обработчик этого события для всей полосы меню. Его следует использовать если Вы хотите чтобы происходило какое-то действие при подсвечивании любого элемента меню. |
| EVT_MENU_OPEN | Во зникает когда меню открывается |
После того, как мы обсудили основы создания меню, можно перейти к описанию того, как работать с элементами меню.
10.2 Работа с элементами меню
10.2.1 Как найти определённый элемент меню в меню?
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1,
"Find Item Example")
p = wx.Panel(self)
self.txt = wx.TextCtrl(p, -1, "new item")
btn = wx.Button(p, -1, "Add Menu Item")
self.Bind(wx.EVT_BUTTON, self.OnAddItem, btn)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self.txt, 0, wx.ALL, 20)
sizer.Add(btn, 0, wx.TOP|wx.RIGHT, 20)
p.SetSizer(sizer)
self.menu = menu = wx.Menu()
simple = menu.Append(-1, "Simple menu item")
menu.AppendSeparator()
exit = menu.Append(-1, "Exit")
self.Bind(wx.EVT_MENU, self.OnSimple, simple)
self.Bind(wx.EVT_MENU, self.OnExit, exit)
menuBar = wx.MenuBar()
menuBar.Append(menu, "Menu")
self.SetMenuBar(menuBar)
def OnSimple(self, event):
wx.MessageBox("You selected the simple menu item")
def OnExit(self, event):
self.Close()
def OnAddItem(self, event):
item = self.menu.Append(-1, self.txt.GetValue())
self.Bind(wx.EVT_MENU, self.OnNewItemSelected, item)
def OnNewItemSelected(self, event):
# получаем элемент меню
item = self.GetMenuBar().FindItemById(event.GetId())
text = item.GetText()
wx.MessageBox("You selected the '%s' item" % text)
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show()
app.MainLoop()
def FindMenuInMenuBar(menuBar, title):
pos = menuBar.FindMenu(title)
if pos == wx.NOT_FOUND:
return None
return menuBar.GetMenu(pos)
Таблица 10.4 Методы взаимодействия wx.MenuBar
| Метод | Описание |
|---|---|
| FindMenuItem(menuString, itemString) | Ищет элемент меню с названием itemString в меню с названием menuString. Либо возвращает найденный элемент меню либо wx.NOT_FOUND |
FindItemById(id | Возвращает элемент меню, ассоциированный с переданным идентификатором. Если такого элемента нет — возвращает None. |
GetHelpString(id) SetHelpString(id, helpString) | Методы для получения и установки строки помощи для элемента меню с переданным идентификатором. Если такого элемента меню нет, метод установки вернёт «», а метод установки просто ничего не сделает. |
GetLabel(id) SetLabel(id,label) | Методы получения и установки для отображаемого названия элемента меню с переданным идентификатором. Обрабатывает отсутствующие э лементы так же как и методы для строки помощи. Эти методы следует использовать только после того, как полоса меню была соединена с фреймом. |
Таблица 10.4 Методы взаимодействия wx.Menu
| Метод | Описание |
|---|---|
| FindItem(itemString) | Возвращает элемент меню с названием либо wx.NOT_FOUND |
| FindItemById(id) | Как и у полосы меню |
| FindItemByPosition(pos) | Возвращает элемент меню, находящийся в данной позиции |
| GetHelpString(id) SetHelpString(id, helpString) | Как и у полосы меню |
| GetLabel(id) SetLabel(id,label) | Как и у полосы меню |
10.2.2 Как я могу активировать или деактивировать элемент меню?
Листинг 10.5
import wx
ID_SIMPLE = wx.NewId()
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1,
"Enable/Disable Menu Example")
p = wx.Panel(self)
self.btn = wx.Button(p, -1, "Disable Item", (20,20))
self.Bind(wx.EVT_BUTTON, self.OnToggleItem, self.btn)
menu = wx.Menu()
menu.Append(ID_SIMPLE, "Simple menu item")
self.Bind(wx.EVT_MENU, self.OnSimple, id=ID_SIMPLE)
menu.AppendSeparator()
menu.Append(wx.ID_EXIT, "Exit")
self.Bind(wx.EVT_MENU, self.OnExit, id=wx.ID_EXIT)
menuBar = wx.MenuBar()
menuBar.Append(menu, "Menu")
self.SetMenuBar(menuBar)
def OnSimple(self, event):
wx.MessageBox("You selected the simple menu item")
def OnExit(self, event):
self.Close()
def OnToggleItem(self, event):
menubar = self.GetMenuBar()
enabled = menubar.IsEnabled(ID_SIMPLE)
menubar.Enable(ID_SIMPLE, not enabled)
self.btn.SetLabel(
(enabled and "Enable" or "Disable") + " Item")
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show()
app.MainLoop()
10.2.3 Как прикрепить к элементу меню акселератор (горячую клавишу)
10.6 Добавление горячих клавиш к элементам меню
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1,
"Accelerator Example")
p = wx.Panel(self)
menu = wx.Menu()
# создаём мнемонику
simple = menu.Append(-1, "Simple &menu item")
# создаём акселератор
accel = menu.Append(-1, "&AcceleratedtCtrl-A")
menu.AppendSeparator()
exit = menu.Append(-1, "E&xit")
self.Bind(wx.EVT_MENU, self.OnSimple, simple)
self.Bind(wx.EVT_MENU, self.OnAccelerated, accel)
self.Bind(wx.EVT_MENU, self.OnExit, exit)
menuBar = wx.MenuBar()
menuBar.Append(menu, "&Menu")
self.SetMenuBar(menuBar)
# используем таблицу акселераторов
acceltbl = wx.AcceleratorTable( [
(wx.ACCEL_CTRL, ord('Q'), exit.GetId())
])
self.SetAcceleratorTable(acceltbl)
def OnSimple(self, event):
wx.MessageBox("You selected the simple menu item")
def OnAccelerated(self, event):
wx.MessageBox("You selected the accelerated menu item")
def OnExit(self, event):
self.Close()
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show()
app.MainLoop()
Использование мнемоник
Использование акселераторов
| Акселератор | Клавиша |
|---|---|
| del | Delete |
| delete | Delete |
| down | Down arrow |
| end | End |
| enter | Enter |
| esc | Escape |
| escape | Escape |
| home | Home |
| ins | Insert |
| insert | Insert |
| left | Left arrow |
| pgdn | Page down |
| pgup | Page up |
| return | Enter |
| right | Right arrow |
| space | Space bar |
| tab | Tab |
| up | Up arrow |
). Конструктор wx.AcceleretorEntry выглядит следующим образом:
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1,
"Toggle Items Example")
p = wx.Panel(self)
menuBar = wx.MenuBar()
menu = wx.Menu()
exit = menu.Append(-1, "Exit")
self.Bind(wx.EVT_MENU, self.OnExit, exit)
menuBar.Append(menu, "Menu")
menu = wx.Menu()
menu.AppendCheckItem(-1,"Check Item 1")
menu.AppendCheckItem(-1,"Check Item 2")
menu.AppendCheckItem(-1,"Check Item 3")
menu.AppendSeparator()
menu.AppendRadioItem(-1,"Radio Item 1")
menu.AppendRadioItem(-1,"Radio Item 2")
menu.AppendRadioItem(-1, "Radio Item 3")
menuBar.Append(menu, "Toggle Items")
self.SetMenuBar(menuBar)
def OnExit(self, event):
self.Close()
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show()
app.MainLoop()
передать идентификатор элемента, который Вы хотите проверить. Кончено, тут есть ожидаемые ограничения — метод полосы меню работает только если она уже прикреплена к фрейму, а метод меню работает для элементов, которые находятся в этом меню. Для установки состояния можно использовать их методы Check(id, check) c очевидными аргументами.
Листинг 10.5
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1,
"Sub-menu Example")
p = wx.Panel(self)
menu = wx.Menu()
submenu = wx.Menu()
submenu.Append(-1, "Sub-item 1")
submenu.Append(-1, "Sub-item 2")
# добавляем подменю
menu.AppendMenu(-1, "Sub-menu", submenu)
menu.AppendSeparator()
exit = menu.Append(-1, "Exit")
self.Bind(wx.EVT_MENU, self.OnExit, exit)
menuBar = wx.MenuBar()
menuBar.Append(menu, "Menu")
self.SetMenuBar(menuBar)
def OnExit(self, event):
self.Close()
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show()
app.MainLoop()
10.3.2 Как создавать всплывающие меню?
Рисунок 10.6
Листинг 10.9
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1,
"Popup Menu Example")
self.panel = p = wx.Panel(self)
menu = wx.Menu()
exit = menu.Append(-1, "Exit")
self.Bind(wx.EVT_MENU, self.OnExit, exit)
menuBar = wx.MenuBar()
menuBar.Append(menu, "Menu")
self.SetMenuBar(menuBar)
wx.StaticText(p, -1,
"Right-click on the panel to show a popup menu",
(25,25))
# создаём меню
self.popupmenu = wx.Menu()
# наполняем его
for text in "one two three four five".split():
item = self.popupmenu.Append(-1, text)
self.Bind(wx.EVT_MENU, self.OnPopupItemSelected, item)
# связываем всплывающее меню с событием для его вызова
p.Bind(wx.EVT_CONTEXT_MENU, self.OnShowPopup)
# показываем всплывающее меню
def OnShowPopup(self, event):
pos = event.GetPosition()
pos = self.panel.ScreenToClient(pos)
sel f.panel.PopupMenu(self.popupmenu, pos)
def OnPopupItemSelected(self, event):
item = self.popupmenu.FindItemById(event.GetId())
text = item.GetText()
wx.MessageBox("You selected item '%s'" % text)
def OnExit(self, event):
self.Close()
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show()
app.MainLoop()
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1,
«Fancier Menu Example»)
p = wx.Panel(self)
menu = wx.Menu()
bmp = wx.Bitmap(«open.png», wx.BITMAP_TYPE_PNG)
item = wx.MenuItem(menu, -1, «Has Open Bitmap»)
# добавляем собственную картинку
item.SetBitmap(bmp)
menu.AppendItem(item)
if 'wxMSW' in wx.PlatformInfo:
font = wx.SystemSettings.GetFont(
wx.SYS_DEFAULT_GUI_FONT)
font.SetWeight(wx.BOLD)
item = wx.MenuItem(menu, -1, «Has Bold Font»)
# изменяем шрифт
item.SetFont(font)
menu.AppendItem(item)
item = wx.MenuItem(menu, -1, «Has Red Text»)
# изменяем цвет
item.SetTextColour(«red»)
menu.AppendItem(item)
menu.AppendSeparator()
exit = menu.Append(-1, «Exit»)
self.Bind(wx.EVT_MENU, self.OnExit, exit)
| Методы | Описание |
|---|---|
| GetBackgroundColour() SetBackgroundColour(colour) | Тип свойства — wx.Colour. Установщику так же можно передать строку с именем цвета wxPython. Управляет фоновым цветом элемента меню. |
| GetFont() SetFont(font) | Управляет шрифтом элемента. Тип — wx.Font |
| GetTextColour() SetTextColour(colour) | Управляет цветом шрифта элемента. Тип как и у методов для цвета фона |
ля сложных приложений полоса меню — главная точка взаимодействия пользователя с вашим приложением. Правильно составленное меню может значительно облегчить работу с вашей программой; неверная же компоновка может сделать её использование невозможным. Принимая во внимание сказанное выше, приступим к обсуждению советов по созданию меню.
10.4.2 Логичная группировка элементов
Твёрдо придерживайтесь стандартов при упорядочивании меню
Предоставьте простой доступ для часто используемых элементов
Пользователю всегда проще использовать те элементы меню, которые расположены выше, чем те, что расположены ниже, поэтому наиболее часто используемые элементы должны находиться вверху меню. Единственное исключение в том, что эксперимент показывает, что второй сверху элемент доступнее первого.
Используйте понятные названия для меню
Помните, что ширина меню в полосе меню пропорциональна длине имени меню, а ширина меню определяется по элементу меню с самым длинным именем. Постарайтесь не давать меню верхнего уровня имена короче четырёх букв. Мы рекомендуем, за исключением общепринятых обозначений, давать настолько длинные имена, чтобы суть их была однозначна ясна. Не бойтесь длинных имен для меню, но и не забывайте, что читать 30-40 символом может быть утомительно.
Помните про многоточия, когда элемент ведёт к диалоговому окну
Все элементы меню, которые вызывают диалоговые окна, должны заканчиваться на многоточие (…)
Придерживайтесь стандартных горячих клавиш
Для горячих клавиш используйте общепринятые стандарты соответствия комбинаций клавиш действиям, приведённые в таблице 10.8
Для возврата действия нет общепринятой комбинации; Вы можете встретить как ctrl+y так и alt+z, или другие наборы клавиш. Если Вы предоставляете большое количество дополнительных горячих клавиш, то рекомендуется дать пользователю возможность их переназначать. Горячие клавиши особенно полезны в приложениях, гд пользователю приходится много печатать, например в текстовых редакторах, и они могут быть бесполезны там, где пользователь много работает мышкой.
| Комбинация клавиш | Функция |
|---|---|
| Crtl+a | Выделить всё |
| Crtl+c | Копировать |
| Crtl+f | Искать |
| Crtl+g | Искать далее |
| Crtl+n | Новое |
| Crtl+o | Открыть |
| Crtl+p | Печатать |
| Crtl+q | Выйти |
| Crtl+s | Сохранить |
| Crtl+v | Вставить |
| Crtl+w | Закрыть |
| Crtl+x | Вырезать |
| Crtl+z | Отменить |
Показывайте текущий статус переключателей
Реализация импорта в Питоне переписана на чистый Питон
Python 3.3 продолжает радовать. Сегодня в него вошли изменения от Bratt Cannon. Теперь import реализован почти весь на питоне, в библиотеке importlib. Она и раньше присутствовала — но теперь __import__ ведёт напрямую в importlib.
Казалось бы, какая разница? На самом деле всё просто:
Проще поддерживать и развивать. Существующий код в
import.cнелегко читать и ещё сложнее понимать во всех деталях (включая в том числе и кроссплатформенность). Теперь ситуация значительно улучшится.Легче изучать. Повторюсь, понимать реализацию по чтению
Python/import.c(а именно там расположен почти весь код, относящийся к импорту модулей и пакетов) — нелегко. Отчасти потому, что написана наC, в то время как большинства питонщиков этот язык, к сожалению, практически не знаком. Главная же причина в том, что этот механизм эволюционировал потихоньку, счастливо избегая больших переделок — но уверенно вбирая в себя сотни мелких и средних улучшений. Проще и понятней от этого не становилось.Унификация. Сложилась на самом деле парадоксальная ситуация: в то время как PEP 302, исчерпывающе описывает то, как должен работать механизм импорта — Питон этим не пользуется. То есть да, PEP 302 полностью реализован и поддерживается — но стандартный импорт из файловой системы идёт в обход этих правил. Не подумайте плохого — ничего не ломается. Но одновременное существование двух дублирующих систем — одной для импорта из файловой системы, а второй для поддержки PEP 302, — уже не очень хорошая вещь.
Снова вспоминаем про поддержку, развитие и изучение. Почему, например, по умолчанию
sys.meta_pathпустой? Отчего вsys.path_hooksесть толькоzipimporter? Где вообще import hooks для загрузки модулей из файловой системы? Почему Python не прописывает в модулях__loader__, хотя PEP 302 однозначно предписывает это делать?Как, в конце концов, всё работает? Читатель, вы уверены что понимаете механизм во всех деталях?
Документация хороша, но она описывает далеко не все нюансы. Когда я попытался разобраться — мне пришлось потратить немало времени. Потом это вылилось в серию статей, посвящённых импорту: часть 1, часть 2, часть 3, импорт и юникод. На этом терпение закончилось и заключительные статьи серии так и не были опубликованы.
Изучение было крайне увлекательным, но меня не оставляло чувство, что процесс должен быть намного проще, ведь Питон старается избегать чрезмерно усложнённых решений.
Так и произошло. Сейчас импорт уже стал проще и понятней, это просто замечательно. В дальнейших планах — подчистка и сокращение кода на С, сведение его объёма к минимуму.
Немного о скорости. Питоновская реализация примерно на 5% медленней — что никого не беспокоит. Быть может, к версии 3.4 маятник качнётся назад, и наиболее критические части кода будут реализованы на С (как это произошло, например, с модулем io). Сейчас гораздо важнее корректность, — а скорость, и без того вполне приемлемая, вполне может подождать своего часа.
Запасайтесь поп-корном, Python 3.3 будет выпущен в конце августа. Это будет по настоящему выдающаяся версия!
Автор: Andrew Svetlov









