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:
- 1 Getting and setting values in controls in the GUI. 1 Validating values entered by users in controls in the GUI. 1 Getting and setting values in an object. 1 Translating between representations of values for storage/manipulation and for display.
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:
- Formatters, 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 (#1), dispatches to subclasses for interacting with controls (#1), 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 (issue 1).
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:
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 DataFormatter) is to accept any user input, 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). 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.
The Classes
`ObjectAttrValidator`
This is the base class for object attribute validators. See discussion below listing.
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
- Optional packages that might be required,
- Links to other pages with additional techniques that go well with this one
- Limitations of this technique (such as "Drag and drop only seems to work with other wxPython codes).
- Additional reading on this subject
- Concerns you have about areas of this technique you don't fully understand.
Code Sample
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.