Tips_namingconventions   >   StudioTips - Naming Conventions (All Contents)

Naming Conventions

Naming conventions are extremely important. If you are writing code on your own you might not fully appreciate the importance of using consistent naming conventions. The minute you add a 2nd or 3rd developer to a project you realize how important it is to establish and use consistent naming conventions. Using consistent naming conventions also makes it easier when you go back to maintain or enhance code a year or two later.

I recommend you to pick up a copy of the book, Code Complete - A Practical Handbook of Software Construction, written by Steve McConnell. It is probably one of the best books on good programming practices. Chapter 9 - The Power of Data Names, is full of great advice on choosing good names. The following is some of the advice by Steve McConnell.

Studies have shown that the optimum variable name is between 8-20 characters in length.

Too long: NumberOfPeopleOnTheUSOlympicTeam

Too short: N, NP, NTM

Just right: NumTeamMembers

If you pick good names for your libraries, classes, methods, and variables you will make your code much more readable and easier to maintain. Changing names after the fact is difficult because you run the risk of breaking code that referenced the library, class, method, or variable. Think long and hard about names and be consistent.

This section explains the naming conventions used in StudioTips.

Library Names

Keep your library names short but meaningful. Think of each library as a module that has responsiblilty for a certain area of your application, its domain. The library name should communicate the module's domain.

Avoid changing a library name because doing so will break code in the classes in other libraries which access the classes in the library who's name you changed.

By default the library name is the same name as the library's file name, without the file extension. After creating a new library you should immediately set the $defaultname property of the library. Setting the library's default name prevents the name from being changed if you or the user changes the library's file name.

Class Names

Each type of class has a designated prefix:

Class Name Suffixes

The following are designated class name suffixes:

Method Names

The following are some general guidelines for method names:

Public vs Private Methods

Omnis Studio divides methods into two categories:

The issue of public vs. private methods is very important to the object-oriented programming concept of information hiding, which allows you to write less fragile and easier to maintain code.

Public methods are the access points to an object's information and functionality. Private methods are the internal workings of the object.

You can modify private methods and even change private method names without breaking code in other classes.

Action vs Property Methods

As you get into object-oriented programming you will see two distinct categories of public methods.

Protected Methods

When you are working with a superclass/subclass structure there will come a time when you create a private method in the superclass, but then later when working in the subclass realize you need to access the private superclass method.

The simple solution is to to change the private method to a public method by adding a $ prefix to the superclass method name.
actionSuperClassMethod

changes to

$actionSuperClassMethod

The problem with this solution is the method that was intended to be private now shows up as a public method in the Interface Manager along with all the other public methods. The clean public interface to the objects now begins to get messing and non-subclass classes could call this once private method.

Some object-oriented programming languages allow you to create protected methods. In the object-oriented world protected methods can only be accessed by subclasses of a superclass. Omnis does not provide a way to create protected methods.

One solution is to prefix protected methods with $_ (e.g. $_actionMethodName) The $_ prefix naming convention communicates to other classes that this method is meant to be accessed by subclasses of the class. The protected methods group separately in lists below the public methods.

Private Submethods

Public and protected methods are able to call private methods within the same class. This makes for a logical flow of method calls.

If you have a very long private method you may want to break it down into several private submethods which the private method calls. The private submethods are not meant to be called by the public or protected methods.

Private submethods are prefixed with the '_' character. If you are writing a public method and need to call private method, using the '_' prefix for a private submethod makes it clear you in to the public method that you should not call that method directly... it belongs to one or more of the private methods.

Method Dividers

For added clarity method dividers are used to separate the property, action, protected, private, and private submethods.

Each method divider consists of 5 dashes, a space, the methods category title, a space, and 5 dashes.

The following is a list of the method dividers used:

Standard Method Prefixes

The following are standard method name prefixes.

Variable Names

The following are some general guidelines for variable names:

Task Variables

Try to limit the use of task variables. In object-oriented programming you always use the lowest possible scope for each variable. I normally only use task variables to instantiate object classes which need application wide scope.

Task variables are all lower case and usually a shortened name of the object class they point to. The task variable names are misspelled on purpose so that Find & Replace can be used to find all occurrences of a specific task variable in the code.

The following are examples of startup task task variables:

Shorter task variable names look cleaner in the code.

Do fn.$retAlphaNumeric(String) Returns Value

Class Variables

Generally it is best object-oriented programming practice to avoid the use of class variables.

One situation where I always use class variables is input variables for prompts. The user might be running a series of reports and I need to prompt them for the date range of records to include in the report. In that situation I will use the class variables cDateFrom and cDateTo, so that the next time the user is prompted, the last dates they entered will be prefilled with the last dates they entered. (For as long as they have the application open.)

Class variables are prefixed with lower case c.

cVarName is a class variable.

Instance Variables

Use instance variable for any values that need to be visible to all of the methods within a class instance.

Instance variables are prefixed with lower case i.

iVarName is an instance variable.

Local Variables

Local variables are only visible within the method they are declared. Good OO practice uses the lowest scope variable needed. If you can use a local variable, do so. As soon as the method is finished the local value is cleared.

Local variables are not prefixed.

VarName is a local variable.

Parameter Variables

Parameters are a type of local variable.

Parameter variables are prefixed with lower case p.

pVarName is a parameter variable.

Parameter naming conventions are especially important since the parameter names are automatically copied to the Do statement when you drag a public method from the Interface Manager. For that reason, there are a few additional naming conventions for parameters.

List Variables

If there is an instance variable list or row in a class or superclass that could be defined differently for various instances of that class the names, iList or iRow are used.

If there are more list variables the variables are named to clarify what the data they contain. (iVendorsList, BooksList, AuthorsList).

Note that the plural form of the word is used for the list variable name. (VendorsList not VendorList)

Row variables follow the same naming convention as lists except that they use the singular form of the word. (VendorRow)

At one time I would always use the generic local variable names List or Row in short methods where I felt it was clear enough what the data the list or row variable contained. Since then I changed my practice to using list or row variable names which clearly communicate the data the list or row variable contains.

Tip

A coding practice I sometime use is to start with the local variable names List or Row when I write the method and then rename the variable when I finish the code. For example, List is renamed to VendorsList. Omnis kindly changes all occurrances of List to VendorsList for me.

Standard Variable Names

The following are standard variable names:

Variable Prefixes

The following are standard variable name prefixes.

See Parameter Variables for the special parameter prefixes.

Variable Suffixes

The following are standard variable name suffixes:

Column Names

Column names are used for list and row datatype variables. In this document if list is specified you can usually assume we are talking about lists or rows.

Lists can have up to 400 columns. Column names are case-sensitive (at least you should assume they are).

Lists can be defined two different ways:

List Column Names

Lists which are defined from schema classes that are mapped to database tables will use the database column names.

Schema classes which are not mapped to the database (_listdef suffix) use lowercase column names. This is done to make it obvious to developers it is an internal list. Using lowercase column names also prevents code errors caused by case sensitive typos. (e.g. ListVar.SqlClassName vs. ListVar.SQLClassName can cause an error.)

Lists which contain data from Omnis class or object properties use the property name prefixed with the appropriate type.

For example of a list of methods would be defined as follows:

; Define the list
Do MethodsList.$cols.$add('classname',kCharacter,kSimplechar,100)
Do MethodsList.$cols.$add('methodname',kCharacter,kSimplechar,100)
Do MethodsList.$cols.$add('methodref',kItemref)

; Build the list of methods
Do rClass.$appendlist(MethodsList,rClass().$name,rMethod().$name,rMethod.$ref)

As you can see the class name is differentiated from the method name by prefixing the 'name' property as appropriate. In the past I did not prefix the Omnis properties with the appropriate type and it made the code very difficult to read code.

SQL Column Names

The range and variety of naming conventions used for database table and column names is huge. Changing existing database tables and columns is often impossible or not worth the hassle.

This section is provided purely for your information. I have been all over the map on database table and column names over the past years.

The database table naming conventions I am currently using are as follows:

The primary key and foreign key column naming conventions I am currently using are as follows:

Using the _fkey and _pkey suffix makes typing the query join text simple.

WHERE Book_fkey = Book_pkey AND Author_fkey = Author_pkey)

There are several administrative columns which I tend to include in many database tables:

There are several common column syntaxes which I tend to use where applicable.

Abbreviations

To make your code easy for other programmers to follow you should limit the use of abbreviations in your class, method, and variable names.

If the abbreviated form of a word only saves typing 2 letters use the full name. If the abbreviated form of a word saves typing 3 letters only declare an abbreviation if the word is a frequently used in your code.

If you use abbreviations be consistent. Using the abbreviation and not the full name. e.g. If you decide to use the abbreviations 'Col' and 'Var' then don't use 'Column' or 'Variable' in any of variable names.

Whether or not to stick with abbreviations for method and class names is debatable. $createSubLib or $createSubLibrary? $setPKey or $setPrimaryKey? I would lean towards the full name rather the abbreviation for method and class names, but I wouldn't make it a hard and fast rule. If the class or method name is getting too long you may need to use some abbreviations. Code readability is the main concern.

The following is a list of abbreviations used.

Acronyms

As with abbreviations you should be consistent with using acronymns. Always use the uppercase form of acronyms in your class, method, and variable names.

The following acronyms are used:

% Prefixed Variables

You can create a local variable on the fly while you are entering code by prefixing the not yet created variable in your code with a % character. Omnis creates a % prefixed local variable as you leave the code editor field where you typed the % prefixed variable. A single % prefix creates a floating decimal number datatype local variable. A double %% prefix creates a character datatype local variable.

This is a handy feature, but you should not leave the % prefixed local variable as it is. If you reuse the % prefixed variable later on in the method and incorrectly type the % prefixed variable name, Omnis creates a second % prefixed local variable matching your typo. You can imagine the problems that will follow when you have to debug your code.

Immediately after creating the a % prefixed variable, go to the local variable and remove the % prefix from the variable name. For single % prefixed variables you likely wanted an integer datatype so you should also change the floating point number datatype to integer. Using floating point numbers when you really need an integer can cause code errors and reduces performance when used as a loop counter.

The one exception where you will see % prefixed variables for loop counters.

Consistent Names

Using names consistently is important for ensuring code readability. This is a minor point, but the effort to use the declared format of the name is no more effort than using a different format. Once you get into the habit of using the declared format you won't find yourself second guessing what form to use. The following is a list of often used names and the declared format for the name.

Language

What language do we use to write code? If we want to share libraries we should use a common language.

I am a Canadian and we use en-GB (British English) spelling in our country, but the fact is that the common language of the computing world is en-US (US English). People using computer programs in non-English speaking countries are often faced with en-US manuals (especially if you are a developer) so en-US isn't completely new to them. For this reason the I use en-US for writing code and documentation. The choice is yours.

Omnis Naming Conventions

Omnis uses naming conventions for its method parameters as follows:

Examples of the Omnis naming conventions are as follows:

$search(calculation[,bFromStart=kTrue, bOnlySelected=kFalse, bSelectMatches=kTrue, bDeselectNonMatches=kTrue])

$lvardef.$add(cName,type,subtype,iMaxLength,blsparameter)
$menus.$remove(rItem)

$cols.$add(fieldname|cName[,type,subtype,iMaxLength])

Note: | = or

Coding Conventions

This section covers information about the various coding styles and conventions which I use.

Coding conventions and styles evolve as we learn better ways of writing code

$construct vs. $initialize

For this discussion we will talk about object classes. But the discussion can be applied to any Omnis Studio class which can be instantiated.

When you instantiate (open) an object, Omnis sends a $construct message to the object. Omnis recommends that you put your initialization code in the $construct method.

One of the problems I ran into when writing Omnis applications was that if an error occurred during the $construct of an object it was difficult to trap the error because the class which opened the object wasn't the one sending a $construct message to the object. Returning false if an error occurred in the $construct had no effect and you could end up with a cascade of error messages. In earlier versions of Omnis Studio the cascade of errors would result in hitting the method stack limit.

An alternate technique is to add a $initialize method to the class and send a $initialize message to the class instance after it has been instantiated.

ObjectA can then instantiate ObjectB without triggering any methods in ObjectB. ObjectA then sends an $initialize message to ObjectB with any necessary parameters. ObjectB would run through its $initialize method. If an error occurred, ObjectB could log the error and return false to ObjectA. ObjectA could then halt everything and return false to the visual object that sent the request to ObjectA.

Much cleaner and much safer!

Replace $constuct with $initialize and require the class which instantiates an object to send an $initialize message before using the object.

That being said, I do use the $construct method in window classes, and you will find $construct here and there in the my classes. If you don't use $construct to initialize a window being opened, the window will appear to the user before receiving its $initialize message. However, there are work arounds for that, such as opening the window off screen, sending it an $initialize message, and then moving it on to the screen.

Quit method return Something

Every method should return something to the sender.

The method where the error is first detected must immediately log an error with the error handler.

Without following this practice and having the sender check the return value you will inadvertently allow errors to crop into your code causing unexpected behavior and not knowing for certain where the error orginated.

Checking for true or false is simple.

Do List.$getAllRecords() Returns FlagOK
If FlagOK
  ; Continue processing
End if
Quit method FlagOK

It is recommended that you always use a local variable for the return value. (not #F because it is global) The local variable name can be changed to suit the situations. (e.g. FetchStatus, bModified, LoopFlagOK, etc.)

There are a few things you need to consider when checking the return value of property methods or $ret prefixed action methods.

String values and numeric values are easy to check.

Do $retStringValue Return StringValue
If not(isnull(StringValue))
  ; Continue processing
End if
Quit method FlagOK

For list and row values you have to be a bit more careful. The following test will fail even if the $retListVar method returns #NULL.

Do $retListVar Return ListVar
; Testing for null on a list or row variable does not work!
If isnull(ListVar)
  Calculate FlagOK as kFalse
Else
  ; Continue processing
End if
Quit method FlagOK

The way to check to see if the $retListOrRowVar... method returns #NULL is to check the $colcount

Do $retListVar Return ListVar
If ListVar.$colcount=0
  Calculate FlagOK as kFalse
Else
  ; Continue processing
End if
Quit method FlagOK

Even for small private methods that can't result in an error, make it a practice to return true and test the return value. Following this practice will save you time. Its worth spending the few extra seconds it takes to type Quit method kTrue.

When to Exit a Method

When should you exit out of a method? As soon as possible, or at the end of the method?

For the first 5 years I followed the method coding convention of quitting out of a method as early as possible. We'll call this the early exit coding style.

I would quit and exit out of a method if:

  1. An error occurred in the method.
  2. An error was returned by a called method.
  3. Nothing further needed to be done.

Doing so meant that there could be several exit points from a method. My logic for using the early exit coding convention was that it was more efficient to exit the method as soon as possible.

Reading Code Complete by Steve McConnell, and discussions with a computer science graduate convinced me to rethink the early exit coding convention which I had been following.

I started using the method coding convention of no early exits and soon discovered some advantages.

A trick I have found helpful is to declare a local variable, FlagOK, and set its intial calculation to kFalse.

If your method makes calls to other methods which return true or false, you use FlagOK as the return variable. As you step through your method you use If FlagOK or If not(FlagOK) to determine the flow. At the end of the method you simply return FlagOK.

The following code demonstrates the exit early coding style.

; Delete the file
Do FileOps.$deletefile(pFilePath) Returns kErrCode
If kErrCode
   Calculate Mssg as "Unable to delete the file"
   Calculate Dtls as con("File path: ",pFilePath)
   Do $ctask.errhndlr.$logFileOpsError($cmethod,Mssg,Dtls,kErrCode)
   Quit method kFalse
End If

; Create a new file
Do oFileOpsExt.$createfile(pFilePath) Returns FlagOK
If not(FlagOK)
   Calculate Mssg as "Unable to create the file"
   Calculate Dtls as con("File path: ",pFilePath)
   Do $ctask.errhndlr.$logFileOpsError($cmethod,Mssg,Dtls)
   Quit method kFalse
End If

; Write to the new file
Do oFileOpsExt.$writefile(pFileContents) Returns FlagOK
If not(FlagOK)
   Calculate Mssg as "Unable to write to the file"
   Calculate Dtls as con("File path: ",pFilePath)
   Do $ctask.errhndlr.$logFileOpsError($cmethod,Mssg,Dtls)
   Quit method kFalse
End If

Quit method kTrue

There are four possible exit points in the above method.

The following code demonstrates the no early exits coding style.

; Delete the file
Do FileOps.$deletefile(pFilePath) Returns kErrCode
If kErrCode
   Calculate Mssg as "Unable to delete the file"
   Calculate Dtls as con("File path: ",pFilePath)
   Do $ctask.errhndlr.$logFileOpsError($cmethod,Mssg,Dtls,kErrCode)
   Calculate FlagOK as kFalse
Else
   
   ; Create a new file
   Do oFileOpsExt.$createfile(pFilePath) Returns FlagOK
   If not(FlagOK)
      Calculate Mssg as "Unable to create the file"
      Calculate Dtls as con("File path: ",pFilePath)
      Do $ctask.errhndlr.$logFileOpsError($cmethod,Mssg,Dtls)
   Else
      
      ; Write to the new file
      Do oFileOpsExt.$writefile(pFileContents) Returns FlagOK
      If not(FlagOK)
         Calculate Mssg as "Unable to write to the file"
         Calculate Dtls as con("File path: ",pFilePath)
         Do $ctask.errhndlr.$logFileOpsError($cmethod,Mssg,Dtls)
      End If
      
   End If
End If

Quit method FlagOK

There is only one exit point in the above method.

If your method doesn't call other methods or functions which return true or false, you may need to Calculate FlagOK as kTrue in your method somewhere before the Quit method FlagOK.

Visual vs. Non-Visual Classes

Visual classes are classes which the user can see on their screen.

Non-visual classes are the hidden classes.

Visual classes can communicate with the user. Non-visual classes should never directly communicate with the user. Not even error messages. If you aren't used to following this practice it might seem difficult to do... but I can tell you from experience and from reading books by object-oriented developers with a lot more experience than me that respecting this coding convention is important.

There are occasions when an object class or a task class must be considered as a visual class. In some cases this can be restricted to a one or two methods of the class. One example is the Startup_Task. When the user opens a library Omnis Studio sends a $construct message to the Startup_Task. If the startup task hits an error it has no choice but to prompt the user with an error message.

Another situation is an object class which is responsible for running reports. In order to know what records to select for the report the object class has to prompt the user for input. In this case the object class would be considered a visual class. To clearly communicate this exception I add the comment method, $#Visual Class, at the top of the object's class methods.

Pushbutton #SHIFT Breakpoint

This is handy piece of code to put in the $event method of all your pushbutton objects.

On evClick
  If #SHIFT
    Breakpoint
  End If

It has no effect on the runtime user, but it's great for debugging. You simply hold the shift key when you click the pushbutton and can then step through the code.

Flow of Method Calls

Within a class it is a good practice to design your methods so that calls flow from the top down.

  1. Public action methods can call protected methods, or private methods.
  2. Protected methods can call private methods.
  3. Private methods can call other private methods or private submethods.
  4. Private submethods can all other private submethods.

Avoid making method calls that flow upwards.

  1. Private submethods shouldn't call private methods, protected methods, or public action methods.
  2. Private methods shouldn't call protected methods, or public action methods.
  3. Protected methods shouldn't call public action methods and should try to avoid calling other protected methods.
  4. Public action methods should try to avoid calling other public action methods.

Any method can call a property method.

This isn't a hard fast rule, but it has been my experience that following this guideline makes it easier to structure, follow, and maintain your code.

$event Calls event_evEventCode

If you create more than a few lines of code in the $event method, the code can look pretty ugly in a hurry. This is partly due to the fact that the On evEventCode commands don't end all the way back at the left margin.

Treelists are a prime example of where you can end up with a lot of code in the $event method.

A coding convention I've been using to solve this mess is to add a series of private event methods to the object:

event_evClick

event_evOpenContextMenu

event_evDoubleClick

event_evTreelistExpand

The $event method traps the On evEventCode and immediately calls the appropriate event_evEventCode method. The $event method doesn't need to send any parameters because the event parameters have task wide scope.

Empty Defined List Instance Variables

If you find yourself defining the same list variable over and over in different methods of a class it might be worthwhile defining the list variable once during the construct and then simply copying it to a local list variable in the methods where you need the defined list.

By prefixing the defined list variable with 'iEmpty' you communicate to the class' methods not to use the variable directly, but rather use it to copy to other list variables.

; Put this line of code in your $construct method.
Do iEmptyContactsList.$definefromsqlclass('sContact')

; Any methods in the class where you need a fresh copy of the contacts list
Calculate ContactsList as iEmptyContactsList

Working Messages

Working messages are visual, so non-visual objects should not open or close working messages, that is the responsibility of the visual object methods. The trouble is that at times the non-visual object might be running a procedure that takes a long time and you want to provide the user with some kind of visual feedback on what is happening and the progress that is being made.

A solution which I have been using is add a startup task variable named, workingmssg. When a visual class method asks a non-visual object to do a process that could potentially take longer than a few seconds the visual class method opens a working message as follows:

; Open the task scope working message
Calculate workingmssg as "Saving changes..."
Working message Working/-1072741743,-1072741739;50;0;60 {[workingmssg]}

; Update the records
Do List.$doworkBatch() Returns FlagOK
; ... etc.

The non-visual object methods which can take longer than a few seconds recalculate the value of the task variable, workingmssg, and redraw the working message.

; Recalc and redraw working message
Calculate workingmssg as con("Updating ",%L," of ",%LN," records..."
Redraw working message

If the visual object didn't open a working message no harm is down in recalculating the task variable and redrawing the non-existant working message.

By avoiding use of the Omnis commands, Working message, and Close working message, in the non-visual objects we can have one consistent working message up and running through all the procedures.

Make use of this technique even in your visual objects. It only take two more lines of code, and if your public method calls a private method within the same class, you get the same advantage of having one consistent working message throughout all the methods called. If the private method calls a non-visual object, the original working message will be updated. If you later refactor your code and move it to a non-visual object you won't need to add Calculate workingmssg as... to the code because you already took care of that the first time you wrote the code.

Avoid using the Omnis commands, Working message, and Close working message, in protected and private methods as well. They should only be found in the public methods. If you follow the flow of method calls guidelines there won't be any conflicts with multiple methods opening or closing a working message.

Comment Lines

The following are some guidelines that I've started to develop and follow:

White Space

Allowing white space (blank lines) makes your code more readable. Compare the following code examples. Study each method on its own in the method editor. (Scroll up and down) Which is easier to comprehend? I think you will agree that the method without the white space is busy and therefore more difficult to read.

; ----- SAMPLE METHOD CODE WITHOUT WHITE SPACE -----

; Synchronize the database with this application.
Do method _syncEmptyDatabase Returns FlagOK
If FlagOK
   ; Insert new data.
   Do method _insertNewDataRecords Returns FlagOK
   If FlagOK
      ; Get the users from the security object.
      Do method _retUsersList Returns UsersList
      If UsersList.$colcount=0
         Calculate FlagOK as kFalse
      Else If UsersList.$linecount>0
         ; Add the users to the DBMS users.
         Do method _addUsersToDBUsers (UsersList) Returns FlagOK
      End If
      ; Insert empty records.
      Do method _insertEmptyRecords Returns FlagOK
      If FlagOK
         ; Rebuild the string tables (which also rebuilds windows, menus, icons)
         Calculate bRuntime as kTrue
         Do ioRebuildCachedsLists.$reloadStringTables(bRuntime) Returns FlagOK
      End If
   End If
End If
Quit method FlagOK


; ----- SAMPLE METHOD CODE WITH WHITE SPACE -----

; Synchronize the database with this application.
Do method _syncEmptyDatabase Returns FlagOK
If FlagOK
   
   ; Insert new data.
   Do method _insertNewDataRecords Returns FlagOK
   If FlagOK
      
      ; Get the users from the security object.
      Do method _retUsersList Returns UsersList
      If UsersList.$colcount=0
         
         Calculate FlagOK as kFalse
         
      Else If UsersList.$linecount>0
         
         ; Add the users to the DBMS users.
         Do method _addUsersToDBUsers (UsersList) Returns FlagOK
      End If
      
      ; Insert empty records.
      Do method _insertEmptyRecords Returns FlagOK
      If FlagOK
         
         ; Rebuild the string tables (which also rebuilds windows, menus, icons)
         Calculate bRuntime as kTrue
         Do ioRebuildCachedsLists.$reloadStringTables(bRuntime) Returns FlagOK
      End If
   End If
End If
Quit method FlagOK

$appendlist vs. $makelist

Omnis offers two choices for making a list of group members, $appendlist and $makelist.

In my earlier years of writing Omnis code I used $makelist. Later I switched to $appendlist because it allows me define and name the list columns first, making the code that follows easier to read.

; Define and build the list of classes.
Do ClassList.$define()
Do ClassList.$cols.$add('classname',kCharacter,kSimplechar,100)
Do ClassList.$cols.$add('classtype',kInteger,kLongint)
Do ClassList.$cols.$add('classref',kItemref)
Do $ctask.$lib().$classes.$appendlist(ClassList,$ref().$name,$ref.$classtype,$ref)

; Select and remove the non-system classes
Do ClassList.$search($ref.classtype < > kSystemtable)
Do ClassList.$remove(kListDeleteSelected)