== Introduction ==

Like many, I had some trouble wrapping my head around validators. So I started experimenting.
It took me a bit of back-and-forth between experiment and the documentation, but I think I have the hang of them now.

The validator mechanism provides a hook for checking whether a user-entered value in a control is valid. But it doesn't address the meaning of ''valid''--that is left to the application code (as it should be).

I needed a way to validate the attributes of an object, in a window used for editing various objects of the same class (not all at the same time, of course). And I might have several such windows in the same application, for editing different classes of object. To simplify this problem, I came up with this ''object-oriented'' approach to validation for '''''objects.'''''

In particular, I wanted to separate the following responsibilities in the code, all of which are ''assumed'' within the description of a `wx.Validator`:

   1 Getting and setting values in controls in the GUI.

   2 Validating values entered by users in controls in the GUI.

   3 Getting and setting values in an object.

   4 Translating between representations of values for storage/manipulation and for display.

I have split these responsibilities into the following elements:

   * DataFormatters, which are aware of data type, and provide methods for translating between display and storage representations (#4) and for validating user-entered values (#2).

   * A validator base class, which gets and sets object attributes (#3), dispatches to subclasses for interacting with controls (#1), and optionally invokes a Formatter to translate data representations (#4) and validate user-entered values (#2).

   * Validator subclasses, which know how to interact with specific types of controls (#1).

This results in (and from) a somewhat different view of Validators than I have seen heretofore, but I find it works well for me.

== What Objects are Involved ==

This recipe makes use of `wx.PyValidator` and defines the following validator classes:

   * `ObjectAttrValidator` -- Base class for a validator that acts as the intermediary between the attributes of an object and a set of controls. `ObjectAttrValidator` makes use of optional DataFormatters for translation and validation services. The default behavior of the `ObjectAttrValidator` (in the absence of a formatter) is to accept any user input as valid, and simply handle two-way transfer of data between an object and the GUI.

   * `ObjectAttrTextValidator` -- A specialization of `ObjectAttrValidator` for interacting with `wx.TextCtrls`. With DataFormatters, this validator can handle most cases in which the user is expected to type input values.

   * `ObjectAttrSelectorValidator` -- Specialization of `ObjectAttrValidator` for interacting with `wx.ControlWithItems` widgets (`wx.ListBox`, `wx.Choice`, `wx.RadioBox`). Assumes use of `EnumType` as described in [[http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/305271|this recipe]] in the [[http://aspn.activestate.com/ASPN/Cookbook/Python/|ActiveState Python Cookbook]].

   * `ObjectAttrCheckListBoxValidator` -- Validator for `CheckListBox` controls. (See attached source file [[attachment:ObjectAttrValidator2.py|ObjectAttrValidator2.py]])

   * `ObjectAttrRadioBoxValidator` -- Validator for `RadioBox` controls. (See attached source file [[attachment:ObjectAttrValidator2.py|ObjectAttrValidator2.py]])

== The Classes ==

=== ObjectAttrValidator ===

This is the base class for object attribute validators. See discussion below listing.

{{{
#!python
import wx

class ObjectAttrValidator( wx.PyValidator ):
    def __init__( self, obj, attrName, formatter=None, flRequired=True, validationCB=None ):
        super(ObjectAttrValidator,self).__init__()

        self.obj          = obj
        self.attrName     = attrName
        self.flRequired   = flRequired
        self.formatter    = formatter
        self.validationCB = validationCB
}}}
Often, a field may be left blank, but sometimes must not be blank. Hence the `flRequired` flag.

Since there are various ways of notifying the user that a validation error has occurred, `ObjectAttrValidator` uses an optional validation callback for that purpose. This method is called ''every time validation is called, whether the value is valid or not.'' This makes it easy for application code to, for example, highlight a control with a bad value, but remove the highlighting when the value has been corrected.

The validation callback should have the following signature:

{{{        def validationCB( obj, attrName, value, flRequired, flValid )}}}

{{{
    def Clone( self ):
        """
        Return a new validator for the same field of the same object.
        """
        return self.__class__( self.obj, self.attrName, self.formatter,
                self.flRequired, self.validationCB )
}}}
'''Development note:''' `Clone` is ''required'' for all Validators. Just make sure you include ''all'' significant parameters when you clone a Validator. I lost far too much time tracking down bugs when I forgot to add a parameter in the constructor call in `Clone`.
{{{
    def SetObject( self, obj ):
        self.obj = obj
}}}
Since my guiding use case was an editor panel used for editing multiple instances of a class (not at the same time, of course), `ObjectAttrValidator` implements `SetObject` to allow on-the-fly replacement of the object being edited (or removal, by passing `None` for `obj`).
{{{
    def TransferToWindow( self ):
        if self.obj == None:
            # Nothing to do
            return True

        # Copy object attribute value to widget
        val = getattr( self.obj, self.attrName )
        if val == None:
            val = ''
        if self.formatter:
            val = self.formatter.format( val )
        self._setControlValue( val )

        return True
}}}
One of the two core routines for transferring data between object and control.

Writing the value to the control is delegated to subclass' `_setControlValue` method.

`TransferToWindow` transfers the value from the attribute to the control, and invokes a `DataFormatter`, if provided, to convert from storage representation to display representation.
{{{
    def TransferFromWindow( self ):
        if self.obj == None:
            # Nothing to do
            return True

        # Get widget value
        val = self._getControlValue()

        # Check widget value against attribute value; only copy if changed
        # Get object attribute value
        oldVal = getattr( self.obj, self.attrName )
        if self.formatter:
            oldVal = self.formatter.format( oldVal )
        if val != oldVal:
            if self.formatter:
                val = self.formatter.coerce( val )
            setattr( self.obj, self.attrName, val )

        return True
}}}
`TransferFromWindow` transfers the value from the control to the attribute, and invokes a formatter, if provided, to convert from display representation to storage representation.

Reading the value from the control is delegated to subclass' `_getControlValue` method.

I use a home-grown object-persistence mechanism in which each object tracks whether it has been changed. To avoid objects being marked dirty unnecessarily, `TransferFromWindow` checks whether the input value (after optional translation by a formatter) differs from the attribute value before setting the attribute.

'''''Changed 2004.10.13''' - Fixed logic bug in Validate method.''<<FootNote(2004.10.13 - Fixed logic bug in Validate method. Was not processing flRequired if no formatter was assigned.)>>
{{{
    def Validate( self, win ):
        flValid = True
        val = self._getControlValue()
        if self.flRequired and val == '':
            flValid = False
        if flValid and self.formatter:
            flValid = self.formatter.validate( val )
        if self.validationCB:
            self.validationCB( self.obj, self.attrName, val, self.flRequired, flValid )
        return flValid
}}}
`Validate` implements the common portions of the validation sequence:

   * A blank value is valid if the attribute was not specified as required.
   * If no formatter is specified, any non-blank value is valid.
   * If a formatter is specified, the formatter's `validate` method is invoked to determine whether the value is valid.
   * If a validation callback is specified, it is invoked ''after'' the value is validated.

Note that the value is in the display representation, not in the storage representation, at this point. The two may be the same, but may not. For example, a date may be stored in ISO format (e.g., 2004-10-02) but presented in "US local" format (e.g., 10-2-2004).
{{{
    def _setControlValue( self, value ):
        """
        Set the value of the target control.

        Subclass must implement.
        """
        raise NotImplementedError, 'Subclass must implement _setControlValue'

    def _getControlValue( self ):
        """
        Return the value from the target control.

        Subclass must implement.
        """
        raise NotImplementedError, 'Subclass must implement _getControlValue'
}}}

These final two methods simply document and enforce the required subclass methods for interacting with controls.

These routines are only responsible for setting and getting the control value. Any data conversion/formatting will be handled by the calling routine.

=== ObjectAttrTextValidator ===

Implements the required `_setControlValue` and `_getControlValue` methods for interacting with `wx.TextCtrl` widgets. The code is self-explanatory, I think.

`ObjectAttrTextValidator`, in conjunction with DataFormatters that use regular expressions, can handle most cases that call for users to type input strings, including integer and floating-point numbers, telephone numbers, dates, times, etc.
{{{
class ObjectAttrTextValidator( ObjectAttrValidator ):
    """
    Validator for TextCtrl widgets.
    """
    def __init__( self, *args, **kwargs ):
        super(ObjectAttrTextValidator,self).__init__( *args, **kwargs )

    def _setControlValue( self, value ):
        wgt = self.GetWindow()
        wgt.SetValue( value )

    def _getControlValue( self ):
        wgt = self.GetWindow()
        return wgt.GetValue()
}}}

=== ObjectAttrSelectorValidator ===

Implements the required `_setControlValue` and `_getControlValue` methods for interacting with `wx.ListBox`, `wx.ChoiceBox`, and `wx.RadioBox` widgets.

{{{
class ObjectAttrSelectorValidator( ObjectAttrValidator ):
    def __init__( self, obj, attrName, formatter, *args, **kwargs ):
        super(ObjectAttrSelectorValidator,self).__init__(
                obj, attrName, formatter, *args, **kwargs )
}}}
In this case, ''the formatter is required''. The `ObjectAttrSelectorValidator` needs the formatter in order to populate the selector.
{{{
    def _getFieldOptions( self, name ):
        """
        Return list of (id,label) pairs.
        """
        return self.formatter.validValues()
}}}
`ObjectAttrSelectorValidator` Adds the `getFieldOptions` method, which is not present in the `ObjectAttrValidator` base class. This method is assumed to return a list of (id, label) pairs. This list is used to populate the selector.
{{{
    def _setControlValue( self, value ):
        wgt = self.GetWindow()

        # Get options (list of (id,value) pairs)
        options = self._getFieldOptions( self.attrName )
        # Sort alphabetically
        options = [ (opt[1], opt) for opt in options ]
        options.sort()
        options = [ opt[1] for opt in options ]
        # Replace selector contents
        wgt.Clear()
        for id, label in options:
            wgt.Append( label, id )

        # Set selection
        wgt.SetStringSelection( value )
}}}
In addition to setting the value of the control, `_setControlValue` also populates the selection options of the control. As a result, a call to `ctrl.TransferToWindow()` results in a fully-populated control.

A variation on `ObjectAttrSelectorValidator` might implement `_setControlValue` to retrieve the options from the object, to accomodate a state-dependent set of possible options.
{{{
    def _getControlValue( self ):
        wgt = self.GetWindow()
        return wgt.GetStringSelection()
}}}

== Notes ==

Other recipes complementary to this one are:
   * DataFormatters -- Value translation and validation helpers.
   * [[http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/305271|EnumType]] -- Enumerated values referenced by name or number.

Note that while the code imports `wx` for convenience and clarity, the actual dependency is only on `wx.PyValidator`.

This implementation of `ObjectAttrValidator` only does full-value validation. It does not validate partial entries as the user types in characters. Adding this capability should be fairly straightforward, but I haven't had the time to do it yet. If and when I do, I'll post another recipe extending this one.

Depending on the application, it may be preferable to break multiple-element attributes (such as dates, times, and IP addresses) into multiple controls. A multiple-control/multiple formatter version of the `ObjectAttrFormatter` might be applicable in that case, I think. I have not pursued that issue as yet.

I haven't yet decided whether it would be worthwhile to split out the `_setControlValue`/`_getControlValue` operations into another set of helper classes. I have encountered one case in which it would be useful to do so. It would further abstract the validator responsibilities into ''three'' logical entities: a formatter to get/set/validate user data values, a control go-between ('facade') to get/set control values, and a validator to use the other two and hook into the wxPython validation/data-transfer mechanism.

== Code ==

The full source file [[attachment:ObjectAttrValidator2.py|ObjectAttrValidator2.py]] is attached to this recipe.

=== Examples ===

The subclasses described above serve as implementation examples for object attribute validators. A few usage examples are provided here.

==== wx.TextCtrl without validation ====
Accepts any input string as valid (including blanks and empty string). Transfers unmodified string between an object attribute and a `wx.TextCtrl`.

Assumptions:
   * ''person'' is an instance of class Person.
   * Class Person defines an attribute ''firstName''.
{{{
    wgt = wx.TextCtrl( self, -1 )
    validator = ObjectAttrTextValidator( person, 'firstName',
            None, NOT_REQUIRED, self._validationCB )
    wgt.SetValidator( validator )
}}}

==== wx.TextCtrl with ISO date validation ====
Accepts ISO-standard date strings (i.e., YYYY-MM-DD).
See DataFormatters for a description of `DateFormatter`.

Assumptions:
   * ''person'' is an instance of class Person.
   * Class Person defines an attribute ''activeDate''.
{{{
    wgt = wx.TextCtrl( self, -1 )
    validator = ObjectAttrTextValidator( person, 'activeDate',
            DateFormatter(), False, self._validationCB )
    wgt.SetValidator( validator )
}}}

==== wx.Choice ====
Accepts values as defined in an instance of [[http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/305271|EnumType]].
See DataFormatters for a description of `EnumFormatter`.

Assumptions:
   * ''person'' is an instance of class Person.
   * Class Person defines an attribute ''status''.
   * The Person module also defines
{{{        Status = EnumType.EnumType( 'Unknown', 'Good', 'Bad' )}}}
{{{
    wgt = wx.Choice( self, -1 )
    validator = ObjectAttrSelectorValidator( person, 'status',
            EnumFormatter( Person.Status ),
            True, self._validationCB )
    wgt.SetValidator( validator )
}}}

=== A higher level view ===

My full requirement for an editor window is:
   * Provide a single window through which multiple objects of the same class can be edited, one at a time.
   * Allow user to select from a list which object to manipulate.
   * Validate user-entered values against domain requirements.
   * Automatically convert stored values into the necessary representation for presentation to user.
   * Automatically convert values entered by user into the necessary representation for storage.
   * Automatically update domain objects with (converted) values entered by user.
   * Do not allow user to change to a new object if any value entered in a field for a displayed object is invalid.

My application uses a `wx.ListCtrl` to display a list of people. When a user clicks on a person in the list, the edit panel is updated to display values from the selected person. However, if there was a person already displayed ''and'' a value in a control is not valid, the new user is ''not'' assigned to the edit panel and the `ListCtrl`'s selection is set back to the previous person. Changes to all edited records are saved when the window is closed or the user issues a Save command.

My full use case is satisfied through the following approach:
   1. Place the edit widgets on an edit panel.
   2. While constructing the edit panel, record all the edit widgets in a dictionary. I use `self._fieldWidgets[fieldName]=wgt`.
   3. Implement a `SetObject` method. In this method, check whether it is okay to change objects before actually changing to the new object:
      {{{
    flDoSwitch = editPanel.Validate() and editPanel.TransferDataFromWindow()
      }}}
   4. If both `Validate()` and `TransferDataFromWindow()` succeed, assign the new object to the edit panel:
      {{{
    if flDoSwitch:
        editPanel.SetObject( obj )
        editPanel.TransferDataToWindow()
      }}}
   5. In the edit panel, implement a simpler `SetObject` method to update the validators' object references:
      {{{
    def SetObject( self, obj ):
        self.obj = obj
        self._updateValidators()
        return True

    def _updateValidators( self ):
        for name, wgt in self._fieldWidgets.items():
            validator = wgt.GetValidator()
            validator.SetObject( self.obj )
      }}}

== Comments ==

Easy on the brickbats, please. ;-)