Introduction

wxPython provides an excellent and growing collection of controls, with means for sizing, aligning and otherwise manipulating them.

When one builds an application that populates controls with data from a database one needs to make the controls data-aware, which is to say, one needs to make the controls capable of responding to database state changes and also of responding to requests from the database about whether a database field value should be changed, for instance. At the same time, the database needs to be able to update its content from the controls, at the discretion of the user.

This recipe shows a simple way of using a mixin to make at least two of the more commonly used wxPython controls data-aware.

It also shows how to decorate a COM object representing a Microsoft Access Recordset to co-operate with these decorated controls.

What Objects are Involved

The recipe involves no unusual Python objects, only wxPython controls such as wxTextCtrl and wxButton.

Process Overview

Two main pieces are involved, DerivedDBControl and DBMixin.

DerivedDBControl is a procedure that accepts a wxPython control class such as wxTextCtrl, and arranges to create a class which derives from the wxPython class and class DBMixin, as a default.

DBMixin provides storage for the identity of the source field upon whose contents the control is to operate, along with the data source that persists that field. The data source will usually be a database cursor or similar item. In this example, however, for the sake of simplicity, the data source is just an MS Access table.

In the code sample line,

the class DBwxTextCtrl is derived from wxTextCtrl and DBMixin. This implies that this class has all of the attributes of the original wxPython class. In this code, nameEdit is an instance of the derived class that is created in exactly the same way that one would create an instance of wxTextCtrl. To connect the derived class to a datasourc its two properties, source and fieldName, are set appropriately.

The Update button directs the datasource to interrogate its listeners, ie, the decorated controls, to provide their contents, so that these data can be used to update a database row, which is then put back into the database.

The Next button directs the datasource to select the next row of the database, and to ask each of its listeners to update its contents from the newly selected record.

Concerns

Code Sample

As the reader will see, the following is a complete script that illustrates how to use this idea to connect a wxPython control with a simple data "source".

   1 from wxPython . wx import *
   2 
   3 class DBMixin :
   4     
   5     def __init__ ( self ):
   6         
   7         self . _source = None
   8         self . _fieldName = None
   9         
  10     def __getattr__ ( self, attr ) :
  11         
  12         if attr == 'source' :
  13             return self . _getSource ( )
  14         elif attr == 'fieldName' :
  15             return self . _getFieldName ( )
  16         elif attr == 'value':
  17             return self . GetValue ( )
  18         else :
  19             raise AttributeError, attr
  20 
  21     def __setattr__ ( self, attr, value ) :
  22         
  23         if attr == 'source' :
  24             self . _setSource ( value )
  25         elif attr == 'fieldName' :
  26             self . _setFieldName ( value )
  27         elif attr == 'value' :
  28             self . SetValue ( value ) 
  29         else :
  30             self . __dict__ [ attr ] = value
  31 
  32     def _setSource ( self, _source ):
  33         
  34         self . _source = _source
  35         _source . addListener ( self ) 
  36         self . tell (  ) 
  37 
  38     def _getSource ( self ):
  39         
  40         return self . _source
  41 
  42     def _setFieldName ( self, _fieldName ):
  43         
  44         self . _fieldName = _fieldName
  45         self . tell (  ) 
  46 
  47     def _getFieldName ( self ):
  48         
  49         return self . _fieldName
  50 
  51     def tell ( self ):
  52         
  53         if self . _fieldName and self . _source :
  54             self . value = self . _source . getFieldValue ( self . _fieldName )
  55 
  56     def changed ( self ):
  57         
  58         return self . value  != self . _source . getFieldValue ( self . _fieldName )
  59 
  60 def DerivedDBControl ( wx_control, mixin = DBMixin, tell = None ):
  61     
  62     class result ( wx_control, mixin ):
  63         
  64         def __init__ ( self, *args, **kwargs ):
  65             
  66             wx_control . __init__ ( self, * args, ** kwargs ) 
  67             mixin . __init__ ( self ) 
  68             self . id = self . GetId (  ) 
  69             self . wx_control = wx_control
  70             
  71     if tell:
  72         setattr ( result, 'tell', tell ) 
  73     return  result
  74 
  75 DBwxTextCtrl = DerivedDBControl ( wxTextCtrl ) 
  76 DBwxCheckBox = DerivedDBControl ( wxCheckBox )
  77 
  78 class DataSource :
  79     
  80     def __init__ ( self, table ) :
  81         
  82         self . __dict__ [ 'table'] = table
  83         self . __dict__ [ 'listeners' ] = [ ]
  84         self . __dict__ [ 'fieldNames' ] = [ Field . Name for Field in table . Fields ]
  85         
  86     def __getattr__ ( self, attr ) : return getattr ( self . table, attr )
  87         
  88     def __setattr__ ( self, attr, value ) : return setattr ( self . table, attr, value )
  89         
  90     def addListener ( self, listener ):
  91         
  92         self . listeners . append ( listener )
  93         
  94     def anyChanged ( self ) :
  95         
  96         return any ( listener . changed ( ) for listener in self . listeners )
  97         
  98     def updateFieldValues ( self ) :
  99         
 100         for listener in self . listeners :
 101             self . setFieldValue ( listener . fieldName, listener . value )
 102             
 103     def tellListenersUpdate ( self ) :
 104         
 105         for listener in self . listeners :
 106             listener . tell ( )
 107         
 108     def setFieldValue ( self, fieldSelector, value ) :
 109         
 110         if type ( fieldSelector ) is types . IntType :
 111             self . Fields [ fieldSelector ] . Value = value
 112         elif type ( fieldSelector ) is types . StringType :
 113             self . Fields [ self . fieldNames . index ( fieldSelector ) ] . Value = value
 114         else :
 115             raise AttributeError, fieldSelector
 116 
 117     def getFieldValue ( self, fieldSelector ):
 118         
 119         if type ( fieldSelector ) is types . IntType :
 120             return self . Fields [ fieldSelector ] . Value
 121         elif type ( fieldSelector ) is types . StringType :
 122             return self . Fields [ self . fieldNames . index ( fieldSelector ) ] . Value
 123         else :
 124             raise AttributeError, fieldSelector
 125             
 126     def MoveFirst ( self ) :
 127         
 128         self . table . MoveFirst ( )
 129         self . tellListenersUpdate ( )
 130 
 131     def MoveLast ( self ) :
 132         
 133         self . table . MoveLast ( )
 134         self . tellListenersUpdate ( )
 135 
 136     def MoveNext ( self ) :
 137         
 138         self . table . MoveNext ( )
 139         self . tellListenersUpdate ( )
 140 
 141 if __name__ == "__main__":
 142     
 143     class mainWindow ( wxFrame ):
 144         
 145         def __init__ ( self, parent, id, title, datasource ):
 146             
 147             wxFrame . __init__ ( self, parent, -1, title, size = ( 300, 400 ) , style = wxDEFAULT_FRAME_STYLE ) 
 148     
 149             self . mainPanel = wxPanel ( self, -1 ) 
 150             self . mainPanel . SetAutoLayout ( True ) 
 151     
 152             self . datasource = datasource
 153     
 154             self . nameEdit = DBwxTextCtrl ( self . mainPanel, -1, "", ( 10, 10 )  ) 
 155             self . nameEdit . source = self . datasource
 156             self . nameEdit . fieldName = 'NAME'
 157     
 158             self . addressEdit = DBwxTextCtrl ( self . mainPanel, -1, "", ( 10, 40 )  ) 
 159             self . addressEdit . source = self . datasource
 160             self . addressEdit . fieldName = 'ADDRESS'
 161             
 162             self . frenchCB = DBwxCheckBox ( self . mainPanel, -1, "French?", ( 10, 70 ) )
 163             self . frenchCB . source = self . datasource
 164             self . frenchCB . fieldName = 'FRENCH'
 165     
 166             self . saveButton = wxButton ( self . mainPanel, -1, "Update", ( 210, 10 )  ) 
 167             EVT_BUTTON ( self, self . saveButton . GetId ( ), self . handleUpdate ) 
 168     
 169             self . changeButton = wxButton ( self . mainPanel, -1, "Next", ( 210, 40 )  ) 
 170             EVT_BUTTON ( self, self . changeButton . GetId ( ), self . handleNext ) 
 171     
 172             self . Show ( True ) 
 173             
 174         def handleUpdate ( self, event ):
 175             
 176             if self . datasource . anyChanged (  ):
 177                 self . datasource . updateFieldValues ( )
 178                 self . datasource . Update ( )
 179                 
 180         def handleNext ( self, event ) :
 181             
 182             if self . datasource . anyChanged (  ) :
 183                 # ask here whether the user wishes to save the altered record before proceeding
 184                 # and act accordingly
 185                 pass
 186             if self . datasource . AbsolutePosition < self . datasource . RecordCount :
 187                 self . datasource . MoveNext ( )
 188             else :
 189                 self . datasource . MoveFirst ( )
 190     
 191     from win32com . client import Dispatch
 192     conn = Dispatch ( r'ADODB.Connection' )
 193     connString = r'PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA SOURCE=%s' % r'N:\FWOTS\ZODB\vss.mdb'
 194     conn.Open ( connString )
 195     rs = Dispatch ( r'ADODB.Recordset' )
 196     rs.Open ( "[Table]", conn, 1, 3 )
 197             
 198     datasource = DataSource ( rs )
 199     
 200     app = wxPySimpleApp ( )
 201     
 202     frame = mainWindow ( None, -1, "Recipe", datasource ) 
 203     frame . Show ( )
 204     app . MainLoop ( )

Comments

Any comments, please contact Bill Bell.

DataAwareControlsMixin (last edited 2010-02-06 21:21:12 by s235-79)

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