Coyote's Guide to IDL Programming

Defining GetProperty and SetProperty Methods

QUESTION: I'm writing a lot of objects, and every one I write has to have a GetProperty and SetProperty method defined for it. This is getting to be a gigantic pain in the neck. Isn't there a generic way to write these routines so I only have to write them once?

And while I'm complaining, whose idea was it to make GetProperty a procedure!? Wouldn't it be nice to have it as a function, so you could use the function call in an expression, like this:

   Widget_Control, wObject->GetProperty(/ID), Set_Value=myValue

ANSWER: Yes, you're right. Defining GetProperty and SetProperty methods all the time is time consuming and a pain. But real programmers never shy away from hard work if it makes their programs better, and this clearly does. (Scientists, on the other hand, well... Let's just say here are a couple of ideas that might appeal to them.)

Let's consider the GetProperty method first. Suppose we create a simple object called PlotParams, like this:

   PRO PlotParams__Define

      class = { PLOTPARAMS, color:"", linestyle:0L }

   END

The traditional approach would be to define a GetProperty method for this object, like this:

   PRO PlotParams::GetProperty, Color=color, Linestyle=linestyle

      IF Arg_Present(color) THEN color = self.color
      IF Arg_Present(linestyle) THEN linestyle = self.linestyle

   END

And a SetProperty method, like this:

   PRO PlotParams::SetProperty, Color=color, Linestyle=linestyle

      IF N_Elements(color) NE 0 THEN self.color = color
      IF N_Elements(linestyle) NE 0 THEN self.linestyle = linestyle

   END

Thus, if you wanted to check to see if the current linestyle was dashed (Linestyle=1), and change it if it was, you might write code like this:

   self -> GetProperty, Linestyle=thisStyle
   IF thisStyle EQ 1 THEN self -> SetProperty, Linestyle=2

But, naturally, you would prefer to write code like this, in which the GetProperty method is a function that can return the value of any field in the object, simply by specifying the proper keyword:

   IF self -> GetProperty(/LineStyle) EQ 1 THEN self -> SetProperty, Linestyle=2

This is, indeed, possible. In fact, there is no reason an object cannot be defined with both a GetProperty procedure method and a GetProperty function method, in the same file. IDL can keep track of which is which. But, suppose you have no intention of getting multiple properties at once. Then a generic GetProperty function method like the one below might be exactly what you need.

(Please note that this is a bare-bones method. In practice, it could be more complicated that this. And, in particular, it is not always a good idea to give away the fields in an object like this. It can go against the entire point of objects encapsulating their data elements. Sometimes, in real-world programming, we don't allow users to access or change "private" data, which we often designate by placing the underscore character in the first character of the field name. You might have to come up with your own conventions for protecting some kinds of data fields. But that said, this--or something very much like it--often comes in handy in IDL object programming.)

The secret of returning the data fields is to be able to create a structure variable that is identical to the object definition. In practice, this almost always involves the use of the Execute command, because it is not possible to apply the Tag_Names function to the object definition (i.e., names = Tag_Names(self) is not allowed).

   FUNCTION PlotParams::GetProperty, _Extra=extraKeyword

     ; Error handling.

     Catch, theError
     IF theError NE 0 THEN BEGIN
        Catch, /Cancel
        ok = Dialog_Message(!Error_State.MSG)
        RETURN, ""
     ENDIF

     ; Only one property at a time can be returned.

     IF N_Elements(extraKeyword) EQ 0 THEN Message, 'Must indicate which property to return.'
     IF N_Tags(extraKeyword) GT 1 THEN Message, 'Only one property at a time can be returned.'

     ; Pull keyword out of extra structure. It will be in UPPERCASE characters.

     keyword = (Tag_Names(extraKeyword))[0]

     ; Obtain a structure definition of the object class.

     ok =  Execute("struct = {" + Obj_Class(self) + "}")

     ; There should be only one match to the structure fields. If there
     ; are more, then you have used an ambiguous keyword and you need more
     ; characters in the keyword abbreviation.

     index = Where(StrPos(Tag_Names(struct), keyword) EQ 0, count)
     index = index[0]
     IF count GT 1 THEN Message, "Ambiguous keyword. Use more characters in it's specification."
     IF count EQ 0 THEN Message, 'Keyword not found.'

     RETURN, self.(index)
  END

Similarly, a generic SetProperty procedure method can be written like this:

   PRO PlotParams::SetProperty, _Extra=extraProperties

     ; Error handling.

     Catch, theError
     IF theError NE 0 THEN BEGIN
        Catch, /Cancel
        ok = Dialog_Message(!Error_State.MSG)
        RETURN
     ENDIF

     IF N_Elements(extraProperties) EQ 0 THEN Message, 'Must indicate which property to set.'
     properties = Tag_Names(extraProperties)

     ; Obtain a structure definition of the object class.

     ok =  Execute("struct = {" + Obj_Class(self) + "}")

     ; Loop through the various properties and their values.

     FOR j=0L,N_Tags(extraProperties)-1 DO BEGIN

        theProperty = properties[j]
        index = Where(StrPos(Tag_Names(struct), theProperty ) EQ 0, count)
        index = index[0]
        IF count GT 1 THEN Message, "Ambiguous keyword: " + theProperty + ". Use more characters in it's specification."
        IF count EQ 0 THEN Message, 'Keyword not found.'
        self.(index) = extraProperties.(j)

     ENDFOR

   END

Limitations: While such generic GetProperty/SetProperty methods work, they have one serious drawback: the Execute command used in them prevents the code from running on the IDL Virtual Machine, introduced in IDL 6.0. This command is not allowed in save files run on the Virtual Machine.

Dave Burridge, who has worked with me on the Catalyst Object Library, found a fairly elegant work-around. It simply involves adding a single positional parameter to the *__Define procedures of all your objects. In this particular example, the PlotParam__Define procedure is written like this:

   PRO PlotParams__Define, class

      class = { PLOTPARAMS, color:"", linestyle:0L }

   END

With this single change, it is possible to replace the Execute commands in the above GetProperty and SetProperty methods with Call_Procedure commands, which are allowed in the Virtual Machine.

Replace this line:

   ok =  Execute("struct = {" + Obj_Class(self) + "}")

with this line:

   Call_Procedure , Obj_Class(self)+'__define', struct

Now you have two generic methods that can be used to get and set properties on any of your objects. Simply add these routines to the base object that is inherited by any object within your object system, and you don't have to write another GetProperty or SetProperty method ever again!

Google
 
Web Coyote's Guide to IDL Programming