Tips_todo   >   Objectclasses   >   Object Classes (All Contents)

Object Classes

\When I started learning Studio, I disliked object classes and couldn't see how they were supposed to be better than code classes. code classes seemed so much easier to call, create, and use. After a year of working "in the dark using code classes", I attended my first Omnis conference and was "enlightened" by Geir on the beauty of object classes. His line "Anything you can do with a code class, you can do better with an object class" has proved true many times over.

Now object classes are my favourite!

OBJECT CLASS INTERFACE

Omnis has done a fantastic job on the developer interface for object classes.

When you need to use an object class, simply create an object type variable, and set the variable subtype to the object class you need to use. Then Right-click on the variable and select 'Interface manager' from the popup menu. Shazam! Studio opens the 'Interface Manager window' which lists all the methods in the object class along with the parameters. Type a 'Do' statement in your code, then drag and drop a method from the Interface manager ... Studio puts all the parameters in brackets for you!

If you use good consistent naming conventions for your parameters, it will be very obvious to you (and others) what each parameter means. (See Naming Conventions)

You can use the Interface Manager to drag and drop methods from other types of classes, but you can't create a variable and right click on it, the way you can with object classes.

OBJECT CLASS INSTANCES

Object classes can have their own instance variables. This means you can send a bunch of data to an object class at different times, then later tell the object class to process the data. (Just be sure to hold the object class instance open). This gives you the flexibility of breaking a large process into logical steps, rather than having to prepare everything before hand and then sending a whole slug of parameters to a specific method. Also since the object class can store its own instance variable, onces you've told the object something (like a window instance reference) you don't have to keep sending the parameter to the same object class instance.

OBJECT CLASSES CAN EXTEND TASK INSTANCES

Code classes will get you into trouble if you try to create a table class instance inside the code class, you will discover inside the table class that task variables have vanished out of scope.

OBJECT CLASSES CAN HAVE PRIVATE METHODS

If you try to create a complex 'Object' with lots of subroutines using a code class you end up with a lot of visible methods when you want to use the code class. It is confusing trying to find the method you are supposed to call vs. the ones that you shouldn't call. If there is more than one developer working on the project, it is really hard to decide. With object classes you can hide all the subroutines by not prefixing them with $. The method(s) which are to be called from outside are prefixed with $ and will show up in the Interface manager. Much cleaner, much nicer to work with.

Object-oriented code is like a road map. A road map is useful not only for what is shows, but just as much for the detail it DOESN'T show. Imagine trying to use a road map that not only gave you the road, but also listed the topographic, demographic, and annual precipitation information ... not a very useful road map. The same goes for object-oriented Programming ... you just want to show the information that is needed for using the object, not everything inside.

Object classes, they're G-R-E-A-T ! ! !

Object Classes on-the-fly

You can call an object class method in a single line, without creating an object type variable.

The syntax for doing this is as follows:

Do $libs.LIBNAME.$objects.OBJECTNAME.$new().$CUSTOMMETHOD('PARAMETERS') Returns VALUE

This single line of code will instantiate the object, call the method, return the value, and then close the object instance. Studio evaluates the first part up to (), then calls the method in the object, sending along the parameters if any.

"Do" and "Calculate" are equivalent, so the following line of code, though different in format is exactly the same as the previous "Do" statement.

Calculate VALUE as $libs.LIBNAME.$objects.OBJECTNAME.$new().$CUSTOMMETHOD('PARAMETERS')

SETTING OBJECT TYPE VARIABLE SUBTYPE ON THE FLY

If you have an object type variable which has the subtype empty (not pointed to an object) you can set the
subtype using the following syntax:

Do $libs.LIBNAME.$objects.OBJECTNAME.$new() Returns oObjectVariable

Once you've set the object type variable's subtype you can use the variable the way you normally would.

Do oObjectVariable.$METHODNAME(pPARAMETERS) Returns VALUE

WHAT'S THE ADVANTAGE OF "OBJECTS ON THE FLY"?

You don't have to use "objects on-the-fly", in fact I hardly used them before rewriting StudioTips. When rewriting StudioTips, I started with the default library name of NEWStudioTIPS. Partly through the rewrite I changed the default library name to TIPS. Guess what happened to all my object type variables ... they were no longer valid.

Using objects on-the-fly, or setting the object type variable subtype on-the-fly, overcomes this problem if you do it right. In StudioTips I'm always in the $clib, so as long as I know the name of the object, setting the object type variable's subtype at the beginning of a method or $construct of a window is easy.

Do $clib.$objects.OBJECTNAME.$new() Returns oObjectVariable

If the object variable is an instance variable, I'll set it in the window's $construct. If it is a local variable I'll set it at the beginning of the method. Immediately after you set the object type variable's subtype you can use the Interface Manager on the variable to look at the object's methods and drag and drop them into your code.

By setting the subtype using notation, if I change the default name of the library, the object type variable continue to point to the correct object class. (So long as the object name doesn't change)

If you are very familiar with the method names of an object, and the object doesn't have a $construct method then might be easier to simply call the object's method on the fly, without an object type variable.

You decide what's best in each situation.

Quit method kTrue or kFalse

A coding good habit (my opinion) is to end your methods with 'Quit method kTrue'. If the method makes it through to the end properly kTrue is returned to the calling method.

If something goes wrong part way through the method or the desired result is not obtained, then
you can exit the method before the end with 'Quit method kFalse'

EXAMPLE:

The calling method uses:
Do $cinst.$METHODNAME Returns FlagOK
If not(FlagOK)
Stop process, OK message if appropriate
Quit method kFalse
End If

Continue process if flag was true

Quit method kTrue

With object-oriented programming you can't be certain what methods might be calling the object, so I find it is best avoid OK messages in the methods. If there was a problem, return kFalse to the calling method and let the calling method decide how best to inform the user or resolve the problem. The habit of return kTrue or kFalse to the calling method is an easy way of communicating to the calling method whether or not execution was successful or failed to get the desired result.

EXAMPLE:

In my base table class superclass I have a method called $getIDRecord(pIDValue) The ID value is sent to the method. The table class method does a $select and $fetch. After the $fetch I tests $rowfetched to see if a record was fetched. If no record was fetched, Quit method kFalse, otherwise Quit method kTrue.

When I call the method:
Do Row.$getIDRecord('IDVALUE') Returns FlagOK
If not(FlagOK)
Notify the user?
Quit method kFalse
End If

Continue process, then...
Quit method kTrue

This sure beats having to test the value of 'Row' everywhere in my code.

NOTE: An exception to the Quit method returns kTrue or kFalse is with "function type" methods and "oContants"
In those sitatuation you will want to "Quit method return Result" so that you can use these methods inline in your code. See "Constants > Getting Constants" and the demo. See "Using Field Ref or Return Value" in this section.

Sharing an Object Instance

This is maybe an obscure situation but having solved the problem once I thought I may has well add it to StudioTips in case another developer had a similar problem.

Let's say you have an Object A, and create an instance of it. The instance could be in a task instance, or a window instance, or even another object instance. Object A's instance has some values in it. For this discussion we'll assume you want to share the Object A's single instance between 2 different windows. These windows might even be part of separate task instances.

Window A opens an instance of Object A. Window A now wants Window B to share that instance of Object A. You don't want to create additional instances of Object A, you want to share the single instance of Object A.

The trick is to set up an item reference variable in Window B and pass a reference of Object A from Window A to Window B. You must include ".$ref" appended to the end of the Object A instance when passing it to Window B.

Click the Run Demo button to try this out.

Storing Object Instance References

If you want to store a "collection" of object instances in a list, you will hit a bug that exists in Omnis Studio. (Tech Support says it will be fixed in version 4.)

I have wasted MANY hours wondering why I could not get this to work, only to discover it was an Omnis Studio bug. :-( Hopefully this tip, prevents you from doing the same.

What makes this bug tricky to catch, is that is only shows up when you add another line to the list.

Okay, many of you are asking... "What is Doug talking about?". Here's an example: This example uses the "observer design pattern".

Let's say we have a menu called, mSpecial, and we want to allow various objects to register with mSpecial and be notified by mSpecial whenever the user selects a menu item is selected in mSpecial.

We add a public method, $attachObserver(pClassInstanceRef), to mSpecial. Other objects can send an $attachObserver message to mSpecial. If we add an item reference instance variable, iObserverRef, to mSpecial we could set its reference to pClassInstanceRef. The problem is that this would only allow mSpecial to support a single observer. In order to make our code flexible, we decide to add a list variable, "iObserversList", to mSpecial. iObserversList has an item reference column named "InstanceReference". Now we can add as many "observers" as we like by adding a new line to the list each time an $attachObserver message is received.

Each time the user selects a menu item in mSpecial, all the registered "observers" in iObserversList are sent a message notifying them of the menu event that has just occurred.

Everything sounds great up to this point. The trouble is that Omnis Studio (pre v4) has a bug where "object instance references" can't be stored in a list. The references are lost as soon as you add a new line to the list. :-(

STORING OBJECT INSTANCE REFERENCES - BUG WORKAROUND

The following explanation and workaround for this bug has been provided by Tim Stewart.

I guess some memory handling aspect of the underlying Omnis list object does not allocate sufficient discrete resources to separate objects in the list, so they can start conflicting with one another. If you have a list with just one line, it looks as though a contained object retains its integrity, but as soon as you add a second line, item references within the first object become invalid and smartlists get .. dumb! We didn't see the complete loss of table instances Doug mentioned - the overriden $dowork method would still run for example - just ain't much good without any list line stati.

Our workaround for object lists is to store them in a row. We use the column name as the object identifier, so a search works like "Row.Identifier". We haven't noticed any performance differences with the volumes we're currently working with. Initial tests suggest that smartlists retain their status, and the object seems to work. Sending universal messages works with "Row.$cols.$sendall(Row.[$ref.$name].$action)". The obvious limitation is maximum 400 columns,
our current contingency plan will be to use multiple rows with an index list if we approach this number.

Our workaround for item ref lists is to store the text of the $fullname of the reference, having first parsed out "$ivars.", "$tvars." and "$cvars." from the string, and then reevaluate this text into a local item ref where necessary.

Neither is perfect, but they allow us to move forward without a complete redesign of the architecture for this part of our system. We will be cautious about designing anything involving dynamic object collections again though, although this should be a fundamental of OO. I could hear the Java guys chortling as we struggled with this. I am relieved we found a workaround, and glad Omnis are fixing this for version 4.

Use Field Ref or Return Value?

Some programmers use the Quit method [Value] to return the result value to the calling method.

I tend to reserve the return value for kTrue and kFalse, indicating whether or not the method executed correctly

In order reserve the return value for kTrue or kFalse, you need to use 'Field Reference' parameters to 'return' the value

The upside on using Field Reference parameters are:

1. You reserve the return value for kTrue or kFalse
2. You can return more than one result.

The downside on using Field Reference parameters are:

1. You must first calculate the value to a Field, before sending it to the method. (Can't send 'This Value' as a parameter)
2. You can not use Field reference returning methods as 'in-line' methods.

Run the demo for this subject to demonstrate the 2 styles.

The code in the 2 methods is shown below.

Custom Constants

In Omnis Classic you would store your 'global variables' in MOFFs (Memory Only File Formats). With Omnis Studio you can still do that, but it isn't a really clean OO type of a solution.

Click open the "Using object classes" node below to see how you can use class variables in object classes called "oConstants" for storing and retrieving custom constants (or global variables).

Another alternative for storing global variables is using task class variables, as explained by Kevin James. The only downside, which Kevin mentions, is that even though these variables are global in scope they are not globally 'visible' to the developer. You have to memorize them. See Task Class Variables for more info.

Custom Constants Using Object Classes

There are many differing opinions on the best place to store your own global variables. I expect a search for "global variables" or "constants" at OmniGuru will give you lots of different ideas.

I've tried a few different things. I don't have the perfect solution, but I'm pretty happy with it.

I regard constants as 'Memory-Only' type data which gets set up on opening the library, or at the moment a method requests the value of the constant. Therefore, I don't want to store my constants in the 'data file'.

When I first started using Studio, I put my 'constants' in the Startup_Task task variables. This worked until my task variables pane started to get cluttered with all kinds of constants, many of which I only needed occassionally.

I tried using an Omnis Memory-Only file format. This works pretty nice with the F9 catalog. You can view and drag and drop the constants directly into your code. However, I use multiple libraries for my application and any constants in my sub-libraries would get scary to read when I closed the other library which had the memory-only file formats. (They change to the file format mapping number right before your eyes. Makes the code a bit hard to read.)

I tried moving my constants from the Startup_Task task variables to the Startup_Task instance variables. This worked good for decluttering my task variables pane, but I could no longer 'view' the constant names from anywhere in my application, so a spelling error meant the code would not work, and if I forgot the exact spelling, I'd have to go to the Startup_Task, look inside, click the instance variables pane, then go back to my code.

STORING CONSTANTS IN AN OBJECT CLASS's CLASS VARIABLES

I have now settled on storing my constants in the class variables of an object class (Can you tell I like object classes?). I create an object class called "oConstants" which has class variables for each constant I want to store and a matching public method for that constant which returns the constant value.

Using class variables allows me to set up the object method so that if the value is blank, code is executed to calculate the constant value and then return the value, otherwise the value is simply returned. For known constant values I might use the "Init. Val/Calc" column in the variable pane for the class variable value.

I then instantiate the object class "oConstants" as a task variable named "cn" in my Startup_Task. Any method within the scope of the Startup_Task can use the Interface Manager to drag and drop the appropriate constant method into its code. In fact you can use them as "in-line" calculations.

e.g. Calculate WavFilePath as con(cn.$retPathCurrentDir,'sounds',sys(9),'click.wav')

If you have muliple libraries running under different tasks, and need to access the constants in another library, you simply need to make the "oConstants" property $external=kTrue, and make a unique task variable "constantsLIBNAME" variable and then point it to the "oConstants"" of the other LIBNAME.

Note: You don't have set up "oConstants" as a task variable, you can just call it on the fly. Since they use class variables to store the contants, the values are not lost when the object instance closes.

LOOKUP LISTS

I have also started using the above concept for Lookup lists, with an object called "oLookups". If the list is empty when oLookup.$LISTNAME is called, the object class builds the list from the data file, then returns the list to the calling method. Otherwise, if the list has already been built,the list is simply returned. I use an optional parameter pRebuildYN_opt which if set to kTrue, will force the method to rebuild the lookup list.

Hope this helps you with deciding which option works best for storing constants in your application.

Getting Constants

Two of the approaches you can use for getting Constants are:
1. Use a method called $getCONSTANTNAME(pfGetValue)
2. Use a method called $retCONSTANTNAME() Return Value

I prefer the 2nd method because it means less methods in my object class. The 2 methods are shown below.

$getValue(pfGetUserName)
Calculate pfGetValue as cValue
Quit method kTrue

$retValue
Quit method cValue

With the $retValue format, you can use the method in an inline function.
e.g. Calculate Name as con("The Honourable ",cn.$retUserName())

Row Variable Constants

One of the problems I ran into with row variable constants was to be able to user the '$retRow' in an in-line function. I couldn't figure out how to put the constant row directly in-line with the column name.

Thanks to Reg Paling & Weitse Jacobs for the solution. The trick was to return a reference to the row variable.

$retRow(pSetValue_opt) Returns RowReference
If not(isnull(pSetValue_opt))
Calculate cRow as pSetValue_opt
End If
Quit method cRow.$ref ;; Whether a get or set, this method returns the Row

Note: Using $ref as the return value would not work if cRow was replaced with a local variable. The reason is that when the $retRow method has finished the local variable values are destroyed. The calling method will be left referencing a variable that is no longer alive. This happened to me because I had a situation where I was first calculating the constant value to a local variable, then updating the class variable, but made the mistake of returning the row variable reference using the local variable instead of the class variable.

Setting Constants

Two of the approaches you can use for setting Constants are:
1. Add a method called $setCONSTANTNAME
2. Include an optional parameter in your $retCONSTANTNAME method

I prefer the 2nd method because it means less methods in my object class. The 2 methods are shown below.

$setValue(pValue)
Calculate cValue as pValue

$retValue(pSetValue_opt)
If not(isnull(pSetValue_opt))
Calculate cValue as pSetValue_opt
End If
Quit method cValue ;; Whether a get or set, this method returns the current UserName

Custom Constants Using Task Variables

I have been experimenting with Global Variables and the best way to handle them. I have several libraries and I want certain variables to be visible/available from all modules. windows/reports/objects etc.

I thought about MOFFs (memory only file formats) and object classes but was not happy with either.

Task Variables are instantiated so they are not suitable. However, Task Class Variables persist so they are perfectly suitable. I experimented with how to access these task variables and discovered you simply need to prefix them with $ctask. e.g. Calculate $task.tcvUserID as Userf.UserID etc.

Now the trick is this: Everything inside Studio must be performed inside a Task so one simply has to create a supertask and subclass EVERY task from it (either directly or indirectly) and voila... there you have it!

The only downside is that you do not have direct access to the task class variables from the variables pane but ... it works and I'm happy.

P.S. I suggest you prefix these variables with tcv.
Kevin James

Note: There is a bug in Studio v2.4 and 3.0 which causes task class class and instance variable names to be changed occassionally when you copy and paste them in your code. $ctask.tvcName will change to $cvars.tvcSomeOtherName. I reported this to Tech Support in v2.4, Eric Azarcon confirmed that the bug still exists in v3.0.

Data Objects

I'm not sure if "data objects" is the correct term for this tip. If there is a better term which I should be using, please email it to me.

When you get fairly deep into the object-oriented mindset, you start to see that everything in Omnis Studio is an object. Objects have attributes or properties, plus methods that do things. In Omnis these attributes or properties plus methods that do things are represented by public methods.

Let's say we create an object class called "oApplicationProperties".

Now we decide to add some "properities" to this object. A public method is added to oApplicationProperties for each "property". ($ApplicationName, $VersionNumber, $CreatedBy, $LastModifiedDate)

The code behind each of these "property" methods ends with "Quit method VALUE".

You can also make a "property" assignable, by adding a $assign method.
e.g. $LastModifiedDate.$assign(pLastModifiedDate)
The public method name actually includes the ".$assign" suffix in the method name!

You can now instantiate "oApplicationProperties" using an instance variable in a window class, and then use the instance variable name followed by the public method name as the $dataname for the entry fields in the window class.

e.g. Instead of "iList.COLNAME", you would use "ioProperties.$LastModifiedDate"

If there is a $assign method available for a property, Omnis Studio will automatically call the $assign method in the data object just prior to the evAfter event for the entry field.

Thanks goes to Weitse Jacobs for demonstrating "data objects" in Omnis Studio to me.

Data Object Advantages

1. You can use data objects to create a "layer" between your interface layer (window and report classes) and your persistence layer (schemas/queries/table classes). This layer can give you finer control between these classes and the flexibility to use different field names.

2. The $assign suffixed method is called before evAfter allowing you to execute code prior to the evAfter event. There may be situations where this would be useful.

Data Object Problems

There are some drawbacks to using data objects.

1. A list property cannot be directly used in a window object $dataname.

If I have a data object property, $ListOfThings, I can't put that property name directly into a headed list, droplist, combolist, etc. Studio simply won't support it. The work around is to create a list variable in the window class, and "Calculate iList as ioPref.$ListOfThings".

2. Data objects can be used in web client remote forms. To the best of my knowledge remote forms will only support row and list variables.

3. Creating the data objects can be time consuming. You have to create a public method for each property, and a .$assign suffix method for each assignable property.

(In StudioWorks I created a data object superclass which allows you to use a schema class to define "properties" in a data object. The superclass automatically adds new "property" methods for any columns that are added to the schema class. You might question the sanity of going through all that effort, but there are places where data objects improve code readability and reduce code maintenance.)

Data Object Uses

The question remains, are there any practical uses for "data objects".

I have found data objects to be handy for storing and modifying "preferences" properties.

Code Documentor uses an object called "oCodeDocumentorPrefs".

The window class "wTips_CodeDocumentorPrefs" uses ioPrefs.$PropertyName for the $dataname in various fields. Changing a value in wTips_CodeDocumentorPrefs changes the value in the "oCodeDocumentorPrefs" data object. Any code which uses on of the "oCodeDocumentorPrefs" properties will use the new value.

To check out the Code Documentor Preferences object and window,
1. Select "Code Documentor" in the lower tabstrip.
2. Right-click anywhere in the Code Preferences window and select "Code Documentor Preferences"
3. Ctrl/Cmnd+T to check out the fields in the window and the code behind the window.

Playing around with data objects has helped deepen my understanding of Omnis Studio and see object classes (and window classes, menus, toolbars, table classees) from an object-oriented programming viewpoint.

Think about the F6 properties of a window class. ($name, $title, $left, $right)
Calculate Left as $cwind.$left()
Do $cwind.$left.$assign(100)

Do you see the similarities to a data object? Is the window class property "$left" really just a "public method" of the window class? Is there a "$left.$assign" method that just simply doesn't show up in the F6 Property Manager, but does really exist? Does the "$canassign" method simply check for the existing of a ".$assign" suffixed method?

Do you see how you could set up your own "property sheets" using object classes?