TI/Programowanie dla Fizyków Medycznych: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()