Introduction

INCOMPLETE - WORK IN PROGRESS

Like many, I had some trouble with 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 they don'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 type. And I might have several such windows in the same application. 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:

As described in the documentation, Validators are responsible for at least the first two of these, and often for all four. I prefer to break this down further, into:

This results in (and from) a little 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 validators:

The Classes

`ObjectAttrValidator`

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

Toggle line numbers
   1 import wx
   2 
   3 class ObjectAttrValidator( wx.PyValidator ):
   4     def __init__( self, obj, attrName, formatter=None, flRequired=True, validationCB=None ):
   5         super(ObjectAttrValidator,self).__init__()
   6         
   7         self.obj          = obj
   8         self.attrName     = attrName
   9         self.flRequired   = flRequired
  10         self.formatter    = formatter
  11         self.validationCB = validationCB
  12     
  13     def Clone( self ):
  14         """
  15         Return a new validator for the same field of the same object.
  16         """
  17         return self.__class__( self.obj, self.attrName, self.formatter,
  18                 self.flRequired, self.validationCB )

Clone is required for all Validators. Just make sure you include all significant parameters when you clone the 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 ):
        """
        Set or change the object with which the validator interacts.
        
        Useful for changing objects when a single panel is used
        to edit a number of identical objects.
        """
        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 ):
        """
        Transfer data from validator to window.
        
        Delegates actual writing to destination widget to subclass
        via _setControlValue method.
        """
        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
    
    def TransferFromWindow( self ):
        """
        Transfer data from window to validator.
        
        Delegates actual reading from destination widget to subclass
        via _getControlValue method.
        
        Only copies data if value is actually changed from attribute value.
        """
        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
    
    def Validate( self, win ):
        """
        Validate the contents of the given control.
        
        Default behavior: Anything goes.
        """
        flValid = True
        if self.formatter:
            val = self._getControlValue()
            if val == '':
                if self.flRequired:
                    flValid = False
            else:
                flValid = self.formatter.validate( val )
        if self.validationCB:
            self.validationCB( self.obj, self.attrName, val, self.flRequired, flValid )
        return flValid
    
    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'

Special Concerns

This is the place to list anything such as

Code Sample

Toggle line numbers
   1 from wxPython.wx import *
   2 
   3 # put your sample code here

Comments

This is the place that you can add your plans for the future of this page, tell people how to contact you, or leave feedback for the author of the page.

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.