Friday, April 10, 2020

Access and Assign methods can be useful!

Published June 21, 2008 | By Andy Kramek

Access and Assign methods were introduced into VFP back inversion 6.0, but for many developers they are still very much an "unknown feature". my objective today is to show a couple of examples of where access and assign methods can be very useful. let's start by reviewing just what access and assign methods are, and how to create them.

basically these methods are event-driven in the sense that, if one is defined for a property, it is fired whenever the associated event occurs. so for an assign method, the event is (not surprisingly) the assignment of a value to the property. note that it doesn't matter how that assignment is made (i.e. whether you use the store command, or simply an "=") the method is fired. similarly for an access method the triggering event is any reference to the property. again the actual method does not matter; the method fires whether the property's value is being read into a variable, or used in an object reference or even being output using the "?".

you can add access and assign methods to any custom property that you create, and to most of the vfp native properties through the edit property/method dialog. exceptions are the value property of a control and native properties of activex controls (though you can add them to the properties of a vfp ole control that hosts an activex control).

to create an access or assign method for a property all you need to do is define a method that is named for the property plus the suffix "_access" or "_assign". this is handled automatically in the visual designers in both the "new property" and "edit property/method" dialogs where you simply check the appropriate box to create the method. in code you simply declare the method in the class definition like any other method. note that an assign method must define an input parameter to receive the incoming value, while an access method has no parameters at all.

define class xobj as session
  *** define a new property
  ilastid = 0
  *** create an assign method for the new property
  protected function ilastid_assign ( tuinval )
    *** only allows values >= 0
    if vartype( tuinval ) = "n" and tuinval >= 0
      this.ilastid = tuinval
    endif
  endfunc
  *** create an access method for the native datasessionid property
  protected function datasessionid_access
    *** returns the datasession not the datasessionid!
    return this.datasession
  endfunc
enddefine

In this simple definition i have defined a custom property that will only ever accept numeric values that are greater than or equal to zero. trying to set this property to anything else is simply ignored (we could, of course, return an error). in the case of the datasessionid property, the access method means that trying to read the current value of the property will return, instead, the current datasession number. the datasessionid property is, therefore, effectively hidden.


Creating strongly typed properties

One really practical use for an assign method is to create strongly typed properties so that errors cannot occur in your code because a property has an inappropriate value. this can effectively reduce the need to check values repeatedly in code by handling the checking at the point at which a value is assigned. if it fails the test the property rejects the value. the basic methodology is illustrated above, but the code can, and usually is, much more rigorous than indicated there.

One scenario where i use an assign method like this is when storing a record number, or id value. obviously i do not want an invalid number in this case so i use an assign method that forces the value to be an integer in the appropriate range. here is the code from a property used to store the current user id value:


protected function iuserid_assign( tnvalue )
  if vartype( tuinval ) <> 'n' or empty( tnvalue )
    *** not a number, ignore it!
    assert .f. message "user id must be passed as an integer"
  else
    if not int( tnvalue ) == tnvalue
       *** not an integer, ignore it
       assert .f. message "user id must be passed as an integer"
    else
      if tnvalue < 0
         *** not 0 or higher, ignore it
         assert .f. message "user id must be passed as positive integer"
      else
        *** we may allow this one – check it
        select userid from usertable where userid = tnvalue to screen noconsole
        if _tally = 1
          *** this is a valid id
          this.iuserid = tnvalue
        else
          *** ignore it
          assert .f. message "user id must be passed as positive integer"
        endif
      endif
    endif
  endif
endfunc
Notice the use of the "to screen noconsole" in the sql query – this is an old trick that suppresses the output from a query and is useful when we want is to know if a record exists, or how many records meet the criterion – effectively this is executing a sql statement of a type that is not normally permitted in vfp: _tally = select count(*) from [source_table] where [condition] Running code with setall() Another use for an assign method is to enable code to be run on multiple objects using setall(). this method, which exists on all vfp container classes, is used to set the same property to some specific value on all objects in scope that have that property. if an object does not have the property in question, the instruction is ignored in the context of the object and does not cause an error. There are some scenarios is which it would be very useful to be able to run some specific code on all objects on a form, or in some container, but only if they actually have the specified method. but setall() only applies to properties, not methods, and so to do this we would need to loop through the objects collection and use pemstatus() to determine if each object had the relevant method. if so we would call it and then proceed with the next object. A much easier option is to simply create a property with an assign method on the object that calls the required method from inside the assign. after all, there is nothing in the implementation rules that say that an assign method must actually assign a value! so at any time a value is assigned to the property the appropriate method is called. since we can use setall() to set properties we avoid the necessity to loop through the collection and test objects. the code is terribly simple:
protected function iruncode_assign( tnvalue )
  this.updateself()
endfunc
So in the form we can simply have:
thisform.setall( 'iruncode', .t. )
So any object that has the 'iruncode' property will immediately call its own 'updateself()'' or whatever other method is required. you can even create this property and associated assign method as a 'template' method. in other words create the property on your root class but leave the assign method empty. in a specific instance you can then call whatever method you want by simply supplying the necessary code in the assign method. The only caveat to this is that you cannot use this approach when sequential execution is needed because there is no way to control, when using setall() the order in which objects will execute their code. however, you could still set the property explicitly when you need objects to run their code in a specific order although in that case you might as well call the methods directly. An additional benefit of using assign methods to execute code is that the code being called can be hidden from external objects, or even from sub classes, because the property in question is defined at the same level as the method itself and so can execute that code even when the method cannot be called directly. Creating objects inside an access method An access method fires whenever its property is accessed so if that property is used to hold an object reference we can use the access method to determine whether the object exists. if the object is there, we simply return the reference otherwise we can try and create the object on the fly and, if successful, again return the reference. why bother? Well it avoids the necessity of testing an object reference property to ensure that the object is there. here is the code from the access method in a form class that uses our data manager object.
protected function odm_access
if vartype( this.odm ) = "o"
  return this.odm
else
  if pemstatus( _screen, 'odm', 5 ) and vartype( _screen.odm ) = 'o'
    *** there is an object reference out there already, grab it
    this.odm = _screen.odm
  else
    *** add the property (no error if already there...)
    addproperty( _screen, 'odm', null )
    *** and instantiate the data manager
    set procedure to dataclass, dsetbase, datamgr additive
    _screen.odm = createobject( 'xdatmgr', this.cdsntouse )
    if vartype( _screen.odm ) = "o"
      this.odm = _screen.odm
    else
      this.odm = null
    endif
  endif
endif
return this.odm
Our general practice when working is to use a property on the vfp _screen object to hold the object reference to the data manager object. so this code first checks to see if the object exists – if it does it simply grabs the reference and populates the property with it. if there is no data manager, this code adds the property (even if the property already exists this will not cause an error). it then instantiates the data manager object and initializes it by using its own connection information (the cdsntouse property holds this). if the creation succeeds the object reference is returned otherwise a null is returned. By encapsulating the process of instantiating the object like this, we can include it in the root class of our form and forget about it. any form based on this class will now either pick up the existing object reference, or create a new one for all subsequent forms to use, without the need of any specific code in the application start-up. Returning other values when accessing a property Another use for access methods is to return something other than the actual value held in a property. one scenario where you may want to do this is when a property is used to store an id value but what you actually need is the look-up of that value. obviously this can be done in code, but if you frequently need it then it makes sense to handle it in an access method like this:
protected function nkeycode_access
local lcretval
  if seek( this.value, 'keytable', 'keycode' )
    lcretval = keytable.keydesc
  else
    lcretval = ''
  endif
  return lcretval
endfunc
By extension the same methodology could be used to return an object with multiple values – for example an entire record from a table created using scatter name. code similar to that above would simply return a data object, or a null:
protected function cdatakey_access
local loretval
  if seek( this.value, 'keytable', 'keycode' )
    lnsel = select()
    select keytable
    scatter name loretval
    select (lnsel)
  else
    loretval = null
  endif
  return loretval
endfunc

There are many more cases where access and assign methods can be used to simplify your code and i would be interested to hear of any other uses that you have found for these methods.

No comments:

Post a Comment

Writing better code (Part 1)

Writing better code (Part 1) As we all know, Visual FoxPro provides an extremely rich and varied development environment but sometimes to...