вторник, 10 апреля 2012 г.

wxPython in Action. Глава 9. Возможность выбора. Диалоги. (часть 1)

В этой главе рассказывается о том, как:

  • создавать модальные диалоговые окна и окна с сообщениями
  • использовать стандартные диалоги
  • создавать помощников
  • показывать советы при запуске
  • создавать и использовать валидаторы
В то время как фреймы используются для продолжительных взаимодействий с пользователем, диалоги обычно нужны для того, чтобы получить некоторое количество информации от пользователя и передать её на обработку дальше. Зачастую диалоговые окна открываются в модальном режиме, что не позволяет другим фреймам перехватывать события, пока диалог не закрыт. В это главе мы обсудим виды диалогов, доступных в wxPython, хотя кроме них Вы можете создать и свои собственные варианты. Среди предустановленных диалогов есть как простые, только для вывода информации, так и более сложные, например, для открытия файлов.
9.1 Работа с модальными диалогами
Модальные диалоги используются или для того, чтобы быстро вывести / получить какую-либо информацию, либо когда без новых данных от пользователя невозможно двигаться дальше. wxPython содержит несколько функций для отображения таких диалогов: диалог с сообщением, со строкой для ввода и с выбором из списка. В следующем разделе мы посмотрим, как эти диалоги выглядят и как Вы можете облегчить себе жизнь с помощью имеющихся функций.
9.1.1 Как создать модальный диалог
Модальный диалог не позволяет другим виджетам получать события от пользователя до тех пор, пока диалог не будет закрыт. Как видно из рисунка 9.1, по внешнему виду не всегда можно отличить, является ли открытое окно диалогом или фреймом.
рис 9.1
Листинг 9.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()
В этом листинге надо отметить несколько деталей: при инициализации кнопка добавляется непосредственно к диалогу, а не на панель. Вообще, панели гораздо реже используются в диалогах, чем во фреймах. Отчасти потому, что диалоги должны быть как можно проще, но главная причина в том, что функционал панели (стандартный фон и переключение между элементами с помощью табуляции) уже реализован в диалогах.
Для отображения диалога как модального окна используется метод ShowModal(), имеющийся от метода Show(), который используется для отображения фреймов. Ваше приложение останавливается в точке вызова ShowModal()  до тех пор, пока диалоговое окно не будет закрыто. В этот момент единственным, кто может взаимодействовать с пользователем является диалог (понятно, что другие приложения продолжают работать в обычном режиме).
Программно диалог можно закрыть с помощью метода EndModal(retCode), где retCode - числовое значение, которое возвращается методом ShowModal(). Обычно оно используется для того, чтобы сообщить приложению каким образом было закрыто диалоговое окно и на основании этого принять соответствующее решение. При этом важно помнить, что прекращение отображения диалога ещё не значит его уничтожения - даже после этого он продолжает существовать, благодаря чему после его закрытия Вы всё ещё можете получить данные, введённые пользователем. Пример этого мы увидим в следующем разделе.
Возможно, Вы заметили, что в листинге не было определено ни одного обработчика, что должно было заставить Вас задуматься: "Почему же тогда диалог реагирует на нажатие кнопок?" Никакого секрета тут нет - это поведение уже определено в wx.Dialog. Есть два предопределённых ID, которые имеют особое значение для диалогов:
  1. когда нажимается wx.Button с ID равным wx.ID_OK, диалог закрывается и в точку вызова ShowModal() возвращается значение wx.ID_OK
  2. аналогично действует нажатие на кнопку со значением 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
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, после чего вызываем его метод ShowModal().
Используем класс wx.MessageDialog
Используя конструктор класса wx.MessageDialog Вы можете задать сообщение в диалоге и выбрать для него необходимые кнопки:
wx.MessageDialog(parent,message,caption="MessageBox",style=wx.OK|wx.CANCEL,pos=wx.DefaultPosition)
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
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.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().
Кроме того, у нас есть ещё три удобных функции для этих целей:
  1. wx.GetTextFromUser()
  2. wx.GetPasswordFromUser()
  3. 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)
И тут с аргументами у Вас не должно возникнуть проблем. Вводимый текст, как понятно, заменяется звёздочками. Возвращаемые значения аналогичны предыдущей функции.
Если же Вам надо получить от пользователя числовое значение, Вам на помощь придёт wx.GetNumberFromUser():
wx.GetNumberFromUser(message, prompt, caption, value, min=0, max=100, parent=None
А вот тут аргументы уже немного другие: message - это строка, отображаемая над promt, который в свою очередь отображается над полем для ввода. value - числовое значение, представляющее собой значение по умолчанию. min и max обозначают границы допустимого для ввода значения. При нажатии на "ок" в программу возвращается value, преобразованное в long. Если же значение не может быть преобразовано в long или находится за пределами допустимого ввода, функция возвращает -1. Значит, если Вы используете эту функцию для получения отрицательных значений, Вам надо это иметь ввиду.
9.1.4 Как отобразить диалог выбора?
Если на ваш взгляд пустое поле ввода даёт пользователю слишком много свободы, Вы всегда можете ограничить её используя wx.SingleChoiceDialog, который позволяет выбрать только одно значение из предложенного списка. На рисунке 9.4 изображён пример такого диалога:
рис 9.4
Код этого окна, приведённый в листинге 9.4, похож на все другие конструкторы диалогов, о которых мы говорили ранее:
Листинг9.4
import wx if __name__ == "__main__": app = wx.PySimpleApp() choices = ["Alpha", "Baker", "Charlie", "Delta"] dialog = wx.SingleChoiceDialog(None, "Pick A Word","Choices",choices) if dialog.ShowModal() == wx.ID_OK: print "You selected: %s\n" % dialog.GetStringSelection() dialog.Destroy()
Конструктор класса wx.SingleChoiceDialog таков:
wx.SingleChoiceDialog(parent, message, caption, choices, clientData=None, style=wx.OK | wx.CANCEL | wx.CENTRE, pos=wx.DefaultPosition)
Аргументы message и caption не должны вызвать у Вас вопросов, choices - список из строк, которые и составляют допустимые варианты выбора для пользователя. Флаги стиля по умолчанию задают наличие кнопок "ОК" и "Отмена" и расположение диалога в центре окна, что, впрочем, игнорируется в Windows, так же как и значение аргумента pos.
Если Вы хотите установить некоторое значение по умолчанию, используйте метод SetSelection(selection), где аргументом является индекс выбранного значения, а не само это значение. Для того, чтобы получить результат выбора пользователем, используйте или GetSelection() для получения индекса выбранного значения, или GetStringSelection() для получения самого значения.
В качестве замены классу можно использовать две функции. Первая, wx.GetSingleChoice, возвращает выбранную пользователем строку:
wx.GetSingleChoice(message, caption, aChoices, parent=None)aChoices представляет из себя список доступных вариантов выбора. Если пользователь нажимает "ОК" возвращается выбранная строка, иначе Вы получите пустую строку. Соответственно, если один из вариантов пустая строка, Вам эта функция не подойдёт. Зато Вы можете использовать этот вариант:
wx.GetSingleChoiceIndex(message, caption, aChoices, parent=None)
Эта функция использует те же аргументы, что и предыдущая, но возвращает индекс выбранного значения или -1, если пользователь нажимает на "отменить".
9.1.5 Как отобразить диалог прогресса?
Зачастую программа должна прекратить взаимодействие с пользователем и что-то обработать. В таких случаях, обычно, выводится окошко с полосой выполнения задачи, чтобы показать, что программа чем-то занята. В wxPython для этого используется диалог прогресса, показанный на рисунке 9.5
рис 9.5
Простейший код, для отображения этого диалога приведен в листинге 9.5:
Листинг9.5
import wx if __name__ == "__main__": app = wx.PySimpleApp() progressMax = 100 dialog = wx.ProgressDialog("A progress box", "Time remaining", progressMax, style=wx.PD_CAN_ABORT | wx.PD_ELAPSED_TIME | wx.PD_REMAINING_TIME) keepGoing = True count = 0 while keepGoing and count < progressMax: count = count + 1 wx.Sleep(1) keepGoing = dialog.Update(count) dialog.Destroy()
Все опции этого диалога устанавливаются в конструкторе:
wx.ProgressDialog(title, message, maximum=100, parent=None, style=wx.PD_AUTO_HIDE | wx.PD_APP_MODAL)
Но в данном случае аргументы отличаются от тех, что используются в других диалогах. title это текст, отображаемый в заголовке диалога, message отображается в самом диалоге, maximum - максимальное значение, используемое Вами для отображения прогресса. Как можно видеть из рисунка 9.5, пользователь не видит это значение, так что можете спокойно устанавливать то число, которое Вам больше всего подходит.
В таблице приведены флаги стиля, специфичные для wx.ProgressDialog:
СтильОписание
wx.PD_APP_MODALЕсли этот флаг установлен, диалог является модальным для всего приложения, в противном случае - только для родительского окна
wx.PD_AUTO_HIDEДиалог автоматически скрывается, когда достигает своего максимального значения
wx.PD_CAN_ABORTДобавляет кнопку "Отменить". Как обрабатывать её нажатие будет объяснено позже.
wx.PD_ELAPSED_TIMEПоказывает время, которое диалог отображается
wx.PD_ESTIMATED_TIMEПоказывает сколько времени займёт задача, основываясь на прошедшем времени, текущем значении прогресса и максимальном его значении
wx.PD_REMANING_TIMEПоказывает сколько времени ещё требуется для завершения задачи (ESTIMATED_TIME - ELAPSED_TIME)

Для использования диалога прогресса Вам понадобится только его метод Update(value, newmsg=""). Аргумент value представляет из себя новое значение прогресса и вызов этого метода перерисовывает диалог на основе этого и максимального значения, указанного в конструкторе; причём value может быть меньше, больше или равен текущему значению диалога. Если указан аргумент newmsg, то он заменяет текст, отображаемый в окне диалога, что позволяет Вам указать пользователю текущее состояние дел.
Метод Update() обычно возвращает значение True, однако если пользователь нажмёт на кнопку отмены, то при следующем его вызове Вы получите значение False. Это Ваш шанс прореагировать на действия пользователя и, скорее всего, остановить ту задачу, которая сейчас вычисляется. С учётом этого механизма, рекомендуется как можно чаще обновлять полосу прогресса, чтобы оперативно отреагировать на решение пользователя о прекращении задачи.

Комментариев нет:

Отправить комментарий