четверг, 12 апреля 2012 г.

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

9.5 Использование валидаторов
Валидатор - это такой специальный объект wxPython, который облегчает работу с данными в диалогах. Мы уже говорили о них в третьей главе, когда упомянули, что валидатор может быть автоматически вызван системным событием. Более того, мы уже видели, что они передаются в качестве параметров некоторым классам, но так до сих пор и не обсудили что же это такое.
Валидатор выполняет три различные функции:
  1. Проверяет данные в элементах перед закрытием диалогов
  2. Автоматически передаёт данные из / в диалог
  3. Проверяет данные по мере их ввода пользователем
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:")
            
            # используем валидаторы
            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()
  1. Метод Validate() проверяет, что связанные с ним виджеты содержат какой-нибудь текст. Если же это не так, то цвет их фона меняется на розовый
  2. Для использования валидатора мы прикрепляем его к виджету, передавая в качестве аргумента конструктора виджета экземпляр нашего класса
На рисунке 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

# вызывается при открытии диалога
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:\n\n" +
        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:")

        # добавляем валидатор
        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()
Так как метод OnChar() находится в валидаторе, он будет вызван ещё до реакции виджета на ввод символа. Мы так же позволяем виджету обработать этот метод, вызывая метод события Skip() (иначе это повлияло бы на нормальную обработку этого события). Валидатор производит проверку допустимости ввода данных и если ввод содержит недопустимые символы метод Skip() не будет вызван и обработка события на этом завершается. Если у Вас возникнет необходимость, Вы можете связать и другие методы для перехвата валидатором.
Валидаторы - мощный и гибкий механизм обработки данных в вашем приложении. Правильное их применение сделает разработку и обслуживание вашего приложения более лёгким.
9.6 Итог
  • Диалоги используются для взаимодействия с пользователем для получения информации, когда это должно быть сделано достаточно оперативно. Вы можете использовать стандартный класс wx.Dialog для создания своего собственного диалога или использовать один из уже готовых вариантов. Во многих случаях для вызова диалогов есть более удобные функции, облегчающие работу с ними.
  • Диалог может быть отображен модально, то есть препятствуя доступу к другим окнам приложения всё то время, пока он активен. Для этого надо использовать метод диалога ShowModa(), который возвращает значение, соответствующее нажатой кнопке при закрытии диалога. Закрытие модального диалога не уничтожает его и он может быть позже использован повторно.
  • Три стандартных простых диалога: wx.MessageDialog для отображения сообщений, wx.TextEntryDialog для ввода текста пользователем и wx.SingleChoiceDialog для выбора пользователем одного из доступных вариантов.
  • Если ваша программа собирается выполнять длительное действие, Вам на помощь придёт wx.ProgressDialog. Для выбора файлов используется wx.FileDialog, для выбора папки - wx.DirDialog.
  • Для выбора шрифта существует wx.FontDialog а для цвета - wx.ColorDialog. Для работы каждого из них используется дополнительный класс данных
  • Для просмотра миниатюр при выборе файла с изображением используйте wx.lib.imagebrowser.ImageDialog.
  • Вы можете создать мастера с помощью wx.wizard.Wizard для последовательного соединения диалогов. Эти диалоги должны быть экземплярами либо wx.wizard.WizardSimplePage либо wx.wizard.WizardPage. Разница между ними в том, что для простых страниц последовательность должна быть задана до отображения мастера, тогда как последовательность стандартных страниц может быть определена динамически.
  • Советы при запуске легко отображаются с помощью функции wx.CreateFileTipProvider и wx.ShowTip
  • Валидаторы - это полезные объекты, которые позволяют предотвратить закрытие диалога если введены неверные данные или они не введены вообще. Кроме того они могут быть использованы для передачи данных в и из диалога и для проверки вводимых данных "на лету"

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

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