Validators - external lib

Hi,

Is there any external library implementing validators for specific controls/data types?

What should a validator do?

Is it a good idea for the validator to return the expected data type?

For example, FloatValidator for wx.TextCtrl after calling .Validate() sets the .value variable of the validator to the float data type?

For example:

ctrl = wx.TextCtrl(..., validator=FloatValidator())
ctrl.Validator.value = 2.0  # assign value
assert ctrl.TransferDataToWindow()  # pass the converted value to the control
assert ctrl.Value == "2.0"
ctrl.Value = "3.0"  # set new value
if ctrl.Validate():  
    v = ctrl.Validator.value
    assert isinstance(v, float)
    assert v == 3.0

What advantages would this idea have over the usual practice of deriving custom validator classes from the wx.Validator class?

It seems that’s exactly what I had in mind.
I’m just wondering if the correct approach is to assign a value to the control and get the user-entered values ​​using a validator.

import wx


class FloatVlidator(wx.Validator):
    def __init__(self):
        super().__init__()
        self._value = None
        self._ctrl_value = None

    def Clone(self):
        return FloatVlidator()

    def Validate(self, parent) -> bool:
        ctrl = self.GetWindow()
        value = ctrl.GetValue()
        try:
            validated_value = float(value)
        except ValueError:
            wx.MessageBox("Incorrect value")
            return False
        else:
            self._value = validated_value
            return True

    def TransferToWindow(self) -> bool:
        if self._ctrl_value is not None:
            ctrl = self.GetWindow()
            ctrl.SetValue(self._ctrl_value)
        return True

    def TransferFromWindow(self) -> bool:
        ctrl = self.GetWindow()
        value = ctrl.GetValue()
        if value:
            self._ctrl_value = value
        return True

    @property
    def value(self) -> float | None:
        return self._value

    @value.setter
    def value(self, value: float):
        self._value = value
        self._ctrl_value = str(value)


class Frame(wx.Frame):
    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)
        self.ctrl = wx.TextCtrl(self, validator=FloatVlidator(), pos=(10, 10))

        self.ctrl.Validator.value = 2.0  # init value

        self.btn = wx.Button(self, label="Get value", pos=(10, 40))
        self.btn.Bind(wx.EVT_BUTTON, self.on_btn)

        self.Layout()

    def on_btn(self, event):
        if self.Validate() and self.TransferDataFromWindow():
            print(self.ctrl.Validator.value)


if __name__ == "__main__":
    app = wx.App()
    frame = Frame(None)
    frame.Show()
    app.MainLoop()

That is certainly an interesting solution. I don’t remember seeing anything similar before.

My use of validators is fairly simple and mostly based on the examples in the wxPython Demo and the “wxPython In Action” book. In these examples the TransferToWindow() and TransferFromWindow() methods simply return True and the application itself gets the value by calling the TextCtrl.GetValue() and converting the value at that point.

I can see the advantage of your technique in that the validator class can be reused in different applications without repeating the conversion code.

well, for me the advantage of a validator is in the event OnChar in order to distinguish Text and Integer (a commercial GUI also does it without coding)
for the rest I think you make out of a two or three liner a lot of may be confusing coding (especially the difference between Frame and Dialog)
BTW to turn a 1 quietly into a 1.0 is for a lot of people a problem because a 1 means I don’t no more and a 1.0 means the first digit is 0 :roll_eyes:

Honestly, I didn’t get much out of it. I don’t know what you were trying to convey.

two things

  • the class Validator seems to be quite open minded
  • for technically minded people 1 and 1.0 are not equal (in Python they are)

here is a reusable alpha/digit checker (no-frills but just plug in)

import  string
import  wx

class DigitAlphaVal(wx.Validator):
    def __init__(self, flag=None):
        # flag should be 'a' for alpha or 'd' for digit
        super().__init__()
        self.flag = flag.upper()
        if self.flag in ('A', 'D'):
            self.Bind(wx.EVT_CHAR, self.OnChar)

    def Clone(self):
        return self

    def OnChar(self, event):
        key = event.GetKeyCode()

        if key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255:
            event.Skip()
            return

        if self.flag == 'A' and chr(key) in string.ascii_letters:
            event.Skip()
            return

        if self.flag == 'D' and chr(key) in string.digits:
            print(string.digits)
            event.Skip()
            return

        if not wx.Validator.IsSilent():
            wx.Bell()

        # Returning without calling even.Skip eats the event before it
        # gets to the text control
        return

:joy:

How would you implement e.g. EmailValidator?

EDIT:
In addition, such a field does not guarantee that we will get the expected data type because the user can paste text from the clipboard.

well, try this

import  string
import  wx

class DigitAlphaVal(wx.Validator):
    def __init__(self, flag=None):
        # flag should be 'a' for alpha or 'd' for digit
        super().__init__()
        self.flag = flag.upper()
        if self.flag in ('A', 'D'):
            self.Bind(wx.EVT_CHAR, self.OnChar)
            self.textCtrl = None

    def Clone(self):
        return self

    def OnChar(self, event):
        if not self.textCtrl:
            self.textCtrl = self.GetWindow()
            self.textCtrl.Bind(wx.EVT_TEXT, self.evt_text)
            self.ctext = self.textCtrl.GetValue()
        key = event.GetKeyCode()

        if mod := event.GetModifiers():
            event.Skip()
            if mod != 2:
                self.textCtrl.Clear()
        else:
            if key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255:
                event.Skip()
                return

            if self.flag == 'A' and chr(key) in string.ascii_letters:
                event.Skip()
                return

            if self.flag == 'D' and chr(key) in string.digits:
                event.Skip()
                return

            if not self.IsSilent():
                wx.Bell()

        # Returning without calling even.Skip eats the event before it
        # gets to the text control
        return

    def evt_text(self, evt):
        txt = evt.GetString()
        if (self.flag == 'A' and txt.isalpha() or
            self.flag == 'D' and txt.isdecimal()):
            self.ctext = self.textCtrl.GetValue()
        else:
            if not self.textCtrl.GetLastPosition():
                self.ctext = ''
            self.textCtrl.ChangeValue(self.ctext)
            if not self.IsSilent():
                wx.Bell()

:smiling_face_with_tear:

Any idea for EmailValidator and others where it is not possible to check correctness character by character?

that’s not a task for a validator because ‘validation’ depends on some end event, i.e. it is normal text input checking (maybe triggered by EVT_TEXT_ENTER or some button) :sneezing_face: