TI/Programowanie dla Fizyków Medycznych:Validatory

Z Brain-wiki
Wersja z dnia 11:28, 4 cze 2015 autorstwa Tgubiec (dyskusja | edycje)
(różn.) ← poprzednia wersja | przejdź do aktualnej wersji (różn.) | następna wersja → (różn.)

Validatory

Walidatory służą do sprawdzania poprawności danych wpisanych przez użytkownika. Jak pamiętamy z rozdziału prezentującego widgety w wielu przypadkach w konstruktorze był parametr valiadator. Aby napisać własny walidator należy stworzyć klasę dziedziczącą po wx.PyValidator, musi ona mieć metodę Clone zwracającą nowy obiekt walidatora, metodę Validate zwracającą True gdy test się powiódł i False gdy dane nie są poprawne i dwie metody TransferTo(From)Window obsługujące przesyłanie danych do i z okna które wywołało okno zawierające walidowaną kontrolkę. Najłatwiej walidacji używa się w przypadku dialogów, gdyż jest ona automatycznie odpalana przy kliknięciu przycisku o id równym wx.ID_OK, oto przykład:

# -*- coding: utf-8 -*-
import wx

class LessThenIntValidator(wx.PyValidator):
    def __init__(self, a, data, key):
        super(LessThenIntValidator, self).__init__()
        self.a = a
        self.data = data
        self.key = key
    def Clone(self):
        return LessThenIntValidator(self.a, self.data, self.key)
    def Validate(self, win):
        textCtrl = self.GetWindow()
        text = textCtrl.GetValue()
        try:
            if int(text) > self.a:
                raise Exception
            textCtrl.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
            textCtrl.Refresh()
            return True
        except:
            wx.MessageBox("Pole musi zawierać liczbę całkowitą mniejszą od " + str(self.a), "Error")
            textCtrl.SetBackgroundColour("pink")
            textCtrl.SetFocus()
            textCtrl.Refresh()
            return False
    def TransferToWindow(self):
        textCtrl = self.GetWindow()
        textCtrl.SetValue(self.data[self.key])
        return True
    def TransferFromWindow(self):
        self.data[self.key] = self.GetWindow().GetValue()
        return True

class MyDialog(wx.Dialog):
    def __init__(self, data, key, *a, **b):
        super(MyDialog, self).__init__(*a, **b)
        txt = wx.TextCtrl(self, validator = LessThenIntValidator(10, data, key))
        btn = wx.Button(self, id = wx.ID_OK, label = 'OK')
        vBox = wx.BoxSizer(wx.VERTICAL)
        vBox.Add(txt)
        vBox.Add(btn)
        self.SetSizer(vBox)
        self.Show()

class MyFrame(wx.Frame):
    def __init__(self):
        super(MyFrame, self).__init__(None, size = (200, 200))
        self.InitUI()
        self.Show()
    def InitUI(self):
        self.key = 'a'
        self.data = {self.key : ''}
        panel = wx.Panel(self)
        sizer = wx.BoxSizer(wx.VERTICAL)
        self.txt = wx.TextCtrl(panel)
        btn = wx.Button(panel, label = u'pokaż dialog')
        sizer.Add(self.txt, proportion = 1, flag = wx.EXPAND)
        sizer.Add(btn, proportion = 1, flag = wx.EXPAND)
        panel.SetSizer(sizer)
        self.Bind(wx.EVT_BUTTON, self.OnButton, btn)
    def OnButton(self, evt):
        self.data[self.key] = self.txt.GetValue()
        myDialog = MyDialog(self.data, self.key, None)
        myDialog.ShowModal()
        myDialog.Destroy()
        self.txt.SetValue(self.data[self.key])

app = wx.App()
MyFrame()
app.MainLoop()

Trochę trudniej jest gdy chcemy poddać walidacji obiekt klasy wx.Frame - walidację wtedy trzeba wywołać ręcznie i dodać ramce styl wx.WS_EX_VALIDATE_RECURSIVELY:

# -*- coding: utf-8 -*-
import wx

class IntValidator(wx.PyValidator):
    def __init__(self, a):
        super(IntValidator, self).__init__()
        self.a = a
    def Clone(self):
        return IntValidator(self.a)
    def Validate(self, win):
        textCtrl = self.GetWindow()
        text = textCtrl.GetValue()
        try:
            if int(text) < self.a:
                raise Exception
            textCtrl.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
            textCtrl.Refresh()
            return True
        except:
            wx.MessageBox("Pole musi zawierać liczbę całkowitą większą od " + str(self.a), "Error")
            textCtrl.SetBackgroundColour("pink")
            textCtrl.SetFocus()
            textCtrl.Refresh()
            return False
    def TransferToWindow(self):
        return True
    def TransferFromWindow(self):
        return True

class MyPanel(wx.Panel):
    def __init__(self, *a, **b):
        super(MyPanel, self).__init__(*a, **b)
        txt = wx.TextCtrl(self, validator = IntValidator(10))
        self.btn = wx.Button(self, id = wx.ID_OK, label = 'OK')
        vBox = wx.BoxSizer(wx.VERTICAL)
        vBox.Add(txt)
        vBox.Add(self.btn)
        self.SetSizer(vBox)
        
class MyFrame(wx.Frame):
    def __init__(self, *a, **b):
        super(MyFrame, self).__init__(*a, **b)
        myPanel = MyPanel(self)
        self.Show()
        self.Bind(wx.EVT_BUTTON, self.OnOK, myPanel.btn)
        self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY)
    def OnOK(self, evt):
        self.Validate()
app = wx.App()
MyFrame(None)
app.MainLoop()

Często chcielibyśmy móc walidować pola od razu po opuszczeni ich np tabem, można tego dokonać w następujący sposób (wyłapując na poziomie kontrolki event EVT_KILL_FOCUS), ale nie jest to najładniejsze rozwiązanie - zobacz co się dzieje gdy chcesz zamknąć okno przy pomocy przycisku x:

# -*- coding: utf-8 -*-
import wx

class IntValidator(wx.PyValidator):
    def __init__(self, a):
        super(IntValidator, self).__init__()
        self.a = a
    def Clone(self):
        return IntValidator(self.a)
    def Validate(self, win):
        textCtrl = self.GetWindow()
        text = textCtrl.GetValue()
        try:
            if int(text) < self.a:
                raise Exception
            textCtrl.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
            textCtrl.Refresh()
            return True
        except:
            wx.MessageBox("Pole musi zawierać liczbę całkowitą większą od " + str(self.a), "Error")
            textCtrl.SetBackgroundColour("pink")
            textCtrl.SetFocus()
            textCtrl.Refresh()
            return False
    def TransferToWindow(self):
        return True
    def TransferFromWindow(self):
        return True

class MyTextCtrl(wx.TextCtrl):
    def __init__(self, *a, **b):
        super(MyTextCtrl, self).__init__(*a, **b)
        self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
    def OnKillFocus(self, evt):
        self.GetValidator().Validate(self)
        
class MyPanel(wx.Panel):
    def __init__(self, *a, **b):
        super(MyPanel, self).__init__(*a, **b)
        txt = MyTextCtrl(self, validator = IntValidator(10))
        self.btn = wx.Button(self, id = wx.ID_OK, label = 'OK')
        vBox = wx.BoxSizer(wx.VERTICAL)
        vBox.Add(txt)
        vBox.Add(self.btn)
        self.SetSizer(vBox)
        
class MyFrame(wx.Frame):
    def __init__(self, *a, **b):
        super(MyFrame, self).__init__(*a, **b)
        myPanel = MyPanel(self)
        self.Show()
        
app = wx.App()
MyFrame(None)
app.MainLoop()

"Programowanie dla Fizyków Medycznych"