Clarion ABC Templates

Making the Transition to the ABC Templates


So, the first question you'll probably ask yourself (after doing the tutorials in Getting Started and Learning Clarion to become familiar with the new features) is: "How can I leverage what I already know to get up to speed with Clarion ABC as fast as possible?" That's the overall question that this informal article answers. You'll probably be surprised at how much is still the same, how many resources are at your disposal, and how much more power Clarion has put in your hands with the change to OOP technology.

 

For those of you who don't write embedded code (or very little) and mostly let the templates handle all the coding, the answer to the question is: you're already leveraged, you already know it--just keep on working with Clarion as you always have.  You may notice a reduction in the size of your EXEs and an increase in your applications' performance, but you don't really care how we achieve that, now do you!  This article will be the most benefit to those of you who do a substantial amount of coding for yourselves in embed points.


I've heard that OOP is really hard to learn.


For example, a Procedural coding thought might be, "How do I make a window refresh when I want it to?" while the corresponding OOP thought should be, "I am a window.  How do I refresh myself when necessary?"   You need to change your perspective from an external (doing things to objects) to an internal (each object does its own thing) viewpoint.  To help you learn the basic theoretical concepts of OOP, TopSpeed has two articles in the Programmer's Guide (this book is on your Clarion CD in the C5-PG.PDF file): "Easing into OOP" and "Object Oriented Programming."  These two articles both explain the fundamental concepts of OOP theory and how they are implemented in the Clarion Language.  The Language Reference documentation of the CLASS structure and its related components also covers this same ground in a more formal manner.

 

OOP, once you've learned the meaning behind all the "buzzwords," is actually pretty simple--it's just not very easy at first.  The OOP concepts, while simple, are fundamentally different from Procedural coding concepts.  That very difference is what makes OOP seem difficult at first, but it is also that basic difference that gives OOP its power. The result is worth the effort required to attain it.


I've heard that the ABC generated code looks very different.


Yes, For all the various types of procedures, the ABC template generated code gives you the same functionality the 2.003 templates gave you (often more, and with more efficient execution). You can easily see this just by looking at the similarity in the template prompts in the Application Generator.  This means that you can simply continue to choose the functionality your PROCEDURE should perform from the ABC Template prompts just as you did before--without changing the familiar way of working you're already accustomed to!

 

I've heard that writing embed code is very different in ABC.


We've added a new feature to the Clarion language specifically to enable you to keep coding your event handling embeds the same way: new scoping rules. In Clarion, PROCEDURE local variables and ROUTINEs are visible and available for use anywhere within that PROCEDURE and any overridden method of a CLASS declared within that PROCEDURE. This means you can still use local variables and call your ROUTINEs in your embed code exactly as you always have, despite the fact that they are actually in separate procedures. There's no difference here between Clarion 2.003 and Clarion 4 or 5.

 

So what are the new considerations to bear in mind? Here's the biggest one: Realize that your embed code is now being placed in a completely separate procedure--an ABC "method" (procedure) specifically overridden by the templates for the individual PROCEDURE you are creating.

 

There are a couple of implications to this that might not be obvious:

 

Any implicit variables you use are in scope only within that one method. Using the "same" implicit variable in another embed (in another method) now creates a separate instance of that implicit variable unique to that method.

 

For example, in Clarion 2.003 if you assigned a value to X# in EVENT:Accepted for a BUTTON control, then tested the value of X# in EVENT:CloseWindow to see if the BUTTON was pressed, you would be referencing the same X# implicit variable in both statements and your code would work as expected. However, in Clarion ABC generated code, EVENT:Accepted is handled by the ThisWindow.TakeAccepted method and EVENT:CloseWindow is handled by the ThisWindow.TakeWindowEvent method. Since these methods are two separate procedures, you would end up with two different X# implicit variables (one in each method), and your code would definitely NOT work because the value of X# in ThisWindow.TakeWindowEvent would always be zero.

 

Therefore, where you once might have used implicit variables across multiple embed points, you should now explicitly declare local variables. The hidden benefit to this is that by explicitly declaring local variables you are letting the compiler catch any spelling mistakes you might have made in your code (a common problem when using implicit variables).

 

In Clarion 2.003 you might have placed an OMIT statement in one embed and its terminator in another embed to "work around" template generated functionality that you wanted to override. This is very dangerous to do in the ABC templates, because you'll probably OMIT much more code than you intended to, breaking your application in ways that may not be obvious.

 

For example, in Clarion 2.003 if you placed an OMIT in the "EVENT:Accepted -- Before Generated Code" embed point for a control and its terminator in the "EVENT:Accepted After Generated Code" embed point for the same control, you would simply be omitting template generated code for that one control. However, in Clarion ABC generated code, you would be omitting all the code (generated and embed code) for a number of controls along with the call to the PARENT.   TakeAccepted method to handle all the standard EVENT:Accepted functionality.

 

Therefore, when you want to override any standard Template functionality in ABC, just override the appropriate method, issuing a RETURN before the call to the PARENT method, and do not use OMIT. The hidden benefit here is that your ABC generated OOP code is much more flexible and efficient, and you'll probably have less need to override standard template generated functionality.

 

What resources do I have to help me learn to convert my code to ABC ?

Clarion 5 has an application converter to convert your 2.003 applications to use the Clarion ABC Templates.  This converter has two modes: Automatic and Manual. When you operate the converter in Manual mode you have an interactive teaching tool to help you learn all the new ABC ways of coding and it operates on the code that you wrote!  In addition to the Application Converter, the "Late Breaking News" portion of on-line Help has a section called "Converting CW 2.x Applications to CLARION ABC."  This topic contains a table listing a number of common Clarion 2.003 coding practices and their ABC equivalents.



What resources do I have to help me learn to convert my code to ABC ?

Clarion 5 has an application converter to convert your 2.003 applications to use the Clarion ABC Templates. This converter has two modes: Automatic and Manual. When you operate the converter in Manual mode you have an interactive teaching tool to help you learn all the new ABC ways of coding and it operates on the code that you wrote! In addition to the Application Converter, the "Late Breaking News" portion of on-line Help has a section called "Converting CW 2.x Applications to CLARION ABC." This topic contains a table listing a number of common Clarion 2.003 coding practices and their ABC equivalents.



What's with these new Embed Priorities ?

The ABC Templates contain fewer named embed points than the 2.003 templates did, so at first it may look like we've removed some capability.   However, the new Embed Priorities allow each named embed point up to 10,000 logical embed points within the generated code.  This can be pretty confusing until you use the Embeditor to edit your embedded source. Once you can see exactly where the embed point priorities lie in context, you will be able to clearly see where you need to place your code. The priority numbers themselves do not matter--what matters is where the embed point priority lies within the surrounding generated code, and that's why we gave you the Embeditor.


  • Choose Setup : Application Options and from the Action for Legacy Embeds droplist, select Show all and generate all (you can do this while still in your app).
  • Use the Embeds dialog as you always have to write your embedded source.
  • Reset the Action for Legacy Embeds to Show filled and generate all (to highlight the difference between the embed points by labeling the Legacy embeds as such in the Embeditor).
  • Use the Embeditor to edit your source and move your code from the Legacy embeds to the appropriate new embed point (immediately above or below the Legacy embed).

Working this way, you'll find that you're still being productive while you are learning the new ABC embeds, and learning the new ABC Library methods at the same time!

 

Using this process, you will quickly learn the ABC equivalents for your more commonly used embed points, such as these:

Clarion 2.003 embed

Initialize the procedure


After Opening the Window


Beginning of procedure, After Opening Files


Preparing to Process the Window


Before Closing the Window


End of procedure, Before Closing Files


Format an element of the Browse


Before Printing detail section


Activity for each record (Process template)


Browser, Double Click Handler

IF RECORDS(SELF.ListQueue) AND|

KEYCODE() = MouseLeft2

!Place your code here

(you must write the surrounding

IF structure, too)

END


Clarion 4 ABC equivalent

WindowManager Method Executable Code Section,

Init() Priority: ~6500

WindowManager Method Executable Code Section,

Init() Priority: ~8030

WindowManager Method Executable Code Section,

Init() Priority: ~7600

WindowManager Method Executable Code Section,

Ask() Priority: FIRST

WindowManager Method Executable Code Section,

Kill() Priority: ~7300

WindowManager Method Executable Code Section,

Kill() Priority: ~5600

Queue Browser Method Executable Code Section,

SetQueueRecord() Priority: ~5500

Process Manager Method Executable Code Section,

TakeRecord() Priority: FIRST

Process Manager Method Executable Code Section,

TakeRecord() Priority: LAST

Browser Method Executable Code Section,

TakeKey() Priority: FIRST






Yeah, but I heard that writing File Handling code is very different in ABC.


Here's a table of the ABC methods to use in place of the common Clarion language statements:


Clarion 2.003 code

OPEN(File)

SHARE(File)

CheckOpen(File)

CLOSE(File)

ADD(File)

IF ERRORCODE THEN STOP(ERROR()).

PUT(File)

IF ERRORCODE THEN STOP(ERROR()).

DELETE(File)

IF ERRORCODE THEN STOP(ERROR( ))


Clarion 4 ABC Library equivalent

Relate:File.Open() !This ensures all related files are opened,

Relate:File.Open() ! as well as the named file, so Referential

Relate:File.Open() ! Integrity constraints can be enforced.

Relate:File.Close() !This ensures all related files are closed

Access:File.Insert() !These ABC methods all do error handling


Relate:File.Update()


Relate:File.Delete(0) !Parameter suppresses the default confirm



Another common file handling situation is the simple file processing LOOP. In 2.003, you would write code like this:

SET(key,key)

LOOP

NEXT(File)

IF ERRORCODE() THEN BREAK. !Break at end of file

!Check range limits here

!Process the record here

END

And here is the equivalent ABC code:

SaveState = Access:File.SaveFile()

SET(key,key)

LOOP UNTIL Access:File.Next()

!Check range limits here

!Process the record here

END

Access:File.RestoreFile(SaveState) !Undo the "bookmark" (SaveState must be a USHORT)

As you can see, this is all pretty straightforward--only a couple of minor changes to learn.

Another common code construct is getting a record from a file. In 2.003, you might write code like this:

IF File::used = 0

CheckOpen(File)

END

File::used += 1

CLEAR(FIL:record)

Fil:Code = 123

GET(File,FIL:CodeKey)

IF ERRORCODE() THEN CLEAR(FIL:Record).

File::Used -= 1

IF File::used = 0

CLOSE(file)

END

And here is the equivalent ABC code:

Relate:File.Open() !This handles all error conditions

CLEAR(FIL:record)

FIL:Code = 123

Access:File.Fetch(FIL:CodeKey) !Fetch clears the record on errors

Relate:File.Close()

And of course, the file Open and Close method calls can be generated for you if you just add the file to the procedure's File Schematic.

The ABC Library is smart enough to only open a file if it really needs to, making your program more efficient.

Using Clarion's ABC Library methods you write less code to accomplish the same (or more) functionality.


How do I learn about all these new ABC methods ?


Method names have specific meaning in the ABC Library to indicate the type of functionality each method provides. These names are consistently used by all the classes:

AddItem

Adds an item to its object's datastore. The item may be a field, a key, a sort order, a range limit, another object, etc.--anything the object needs to do its job.

Ask[Information]

Interacts with the end user to get the Information.

Fetch

Retrieves data (usually from a file).

GetItem

Returns the value of the named item.

Init

Does whatever is required to initialize the object.

Kill

Does whatever is required to shut down the object, including freeing any memory allocated during its lifetime.

Reset[what or how]

Resets the object and its controls. This includes reloading data, resetting sort orders, redrawing window controls, etc.

SetItem

Sets the value of the named item, or makes the named item active so that other object methods can operate on the active item.

TakeItem

"Takes" the item from another method or object and continues processing it. The item may be a window event (Accepted, Rejected, OpenWindow, CloseWindow, Resize, etc.), a record, an error condition, etc.

Throw[Item]

"Throws" the item to another object or method for handling. The item is usually an error condition.

TryAction

Makes one attempt to carry out the Action. A return value of zero (0 or Level:Benign) indicates success; any other value indicates failure.


Knowing these consistent naming conventions will make it much easier to understand what an object's methods do, whether you've read the Application Handbook about that specific type of object or not!


In addition to the Application Handbook, Clarion also has a Class Viewer to show you the ABC Library properties and methods in a tree view. On any Classes tab, just press the button labeled "Application Builder Class Viewer" to view the ABC Library structure. The Class Viewer graphically shows you how the ABC Library classes are derived--which class inherits properties and methods from which Parent class.

 

The ABC Template set contains two Code Templates, which will help you learn more about using the ABC Library:

CallABCMethod and SetABCProperty. These were specifically created to "walk you through" writing ABC Library code in any executable code embed point. These two Code Templates will write your method calls and object property assignments for you!


So how do I figure out which ABC method to use ?


Here is a standard process you can use to accomplish any task using the ABC Templates and Library:


1) Determine if the ABC Templates can perform the task.


If the ABC Templates will perform the task for you, you're done. If they won't, continue on to Step 2.


2) Identify the ABC Object (and its CLASS) that manages the behavior you need to change.


Object

ThisWindow

BRWn

ThisProcess

ThisWindow

ThisReport

Parent CLASS

WindowManager

BrowseClass

ProcessClass

ReportManager(in Report procedures)

ProcessClass(in Report procedures)

Once you've identified the candidate objects and CLASSes, continue on to Step 3.


3) Determine if the object/CLASS has a property that you can set to accomplish the task.


If setting a property performs the task for you, you're done. If not, continue on to Step 4.


4) Determine if a method of the object/CLASS already does the task.


If calling an existing method performs the task for you, you're done. If no method already does the task, continue on to Step 5.


5) Determine which method you can modify to accomplish the task, then override it.


If the modified/overridden method is VIRTUAL, you're done. You do not need to call VIRTUAL methods yourself since the other methods of the CLASS call them for you as part of their normal operations (as in the above example). If the method is not VIRTUAL or you need to call it outside the normal sequence of events, then you just use a similar process to determine where to call the method: find the method that manages the behavior and embed your call in an embed point of that method.

Following these steps, you can accomplish any programming task in the most efficient manner, by always working at the highest level of abstraction possible in Clarion--that is, do as little "work" yourself as possible to accomplish the greatest effect.


OK, the theory looks good, but how about some real examples ?

Sure. Here're a few:


Task: I want a Browse list to display in descending key order.

1) The Browsebox Control Template's documentation in the Application Handbook tells you that it supports extra sort fields in addition to key fields. If you do not specify a key in the File Schematic, the Browse will sort just by the fields you name as Additional Sort Fields. Therefore, just list the key fields in Additional Sort Fields with a leading minus sign and a comma between each field (such as: -CUS:LastName,-CUS:FirstName). You're done at Step One!

Task: I want to print a report, skipping the print preview if the user has elected not to preview.


2) Opening the Embeditor for the report, you see two objects that appear to be likely candidates to control this behavior: Previewer/PrintPreviewClass and ThisWindow/ReportManager. PrintPreviewClass is pretty obvious. ReportManager is also likely because you can see it has a method called AskPreview (so it might have something to do with the preview functionality). You go on to Step Three.

3) The Application Handbook documents all the public properties for each class. Checking the properties for PrintPreviewClass, you see that there is no property that appears to control whether the print preview executes or not. However, when you check the properties for ReportManager you see a SkipPreview property that does exactly what you need. Therefore, to conditionally suppress print preview based on a user setting in a control file you can simply:


IF NOT CTL:PrintPreview THEN ThisWindow.SkipPreview = TRUE.


Task: I want to let the end-user dynamically filter a browse list at runtime.


2) In the Embeditor, you see that the only likely candidate object/CLASS is BRW1/BrowseClass, because it controls the browse list, then go on to Step Three.

3) None of the BrowseClass properties listed in the Application Handbook appear to help for this task. BrowseClass is derived from the ViewManager, which means it inherits all the ViewManager properties and methods, so you check the ViewManager properties also. None of them apply either, so you go on to Step Four.

4) The Application Handbook documents the BrowseClass methods in addition to its properties. You don't immediately see any that might apply, so you check the inherited ViewManager methods, also. That's where you find the ViewManager's SetFilter method, which appears to be exactly what you need for this task. Therefore, you see that to change the browse filter you can execute code like this:


if NewFilter

BRW1.SetFilter('CUS:LastName[1] = ''' & NewFilter & '''')

else

BRW1.SetFilter('')

end

ThisWindow.Reset(1)


Task: I want the user to be able to copy an existing record when adding a new record.


  • Neither the BrowseBox nor the BrowseUpdateButtons documentation in the Application Handbook shows any template settings for this, so you go on to Step Two.
  • In the Embeditor, you see that the only likely candidate object/CLASS is BRW1/BrowseClass, because it controls the browse list and calling the update Form, so you go on to Step Three.
  • None of the BrowseClass or inherited ViewManager properties listed in the Application Handbook appear to help for this task, so you go on to Step Four.
  • You look at the BrowseClass methods and don't see any that might apply, so you check the inherited ViewManager methods, also.   That's where you find the   ViewManager's PrimeRecord method, which takes a parameter that appears to be exactly what you need for this task. However, this method is VIRTUAL--automatically called by the base classes--so you don't need to call it, so you go on to Step Five.
  • Opening the Embeditor for the Browse procedure, you find the BRW1.PrimeRecord method. You can see that it takes a parameter called SuppressClear which defaults to FALSE (zero). Therefore, you know that you can just assign TRUE (one) to the SuppressClear parameter before it is passed on to PARENT.PrimeRecord to suppress clearing the rest of the fields in the record (after auto-incrementing any necessary fields). However, if you just do a simple assignment statement, you will be overriding this functionality for every insert. Therefore, you need to declare a local flag variable to the Browse procedure to flag whether or not to suppress the clear. Scroll back up to the top of the file and, in any Data Section embed point, add the following code:

CopyFlag BYTE


IF CopyFlag = TRUE

!Check the flag's value

SuppressClear = TRUE

!Set the PrimeRecord parameter value

CopyFlag = FALSE

!Reset the flag

END


You're almost done, but not quite. In order for the user to signal when they want to copy a record, you need to add a "Copy" BUTTON control to the Browse window. Then, in EVENT:Accepted for your ?Copy BUTTON, you place the following code to set the CopyFlag and initiate adding the copied record:


CopyFlag = TRUE

!Set the flag and then trigger

POST(EVENT:Accepted,?Insert)

! the Insert button's code to execute as normal

When the user presses your Copy button, CopyFlag is set to TRUE and then the normal Insert button code sequence occurs, but your overridden BRW1.PrimeRecord method will execute instead of the ABC Library's standard method. You're done at Step Five!


Task: I want to add my new "Copy" button to the Browse's popup menu.


  • The BrowseBox Control Template documentation in the Application Handbook doesn't show any template settings for this, so you go on to Step Two.
  • In the Embeditor, you see that the only likely candidate object/CLASS is BRW1/BrowseClass, because it controls the browse list, then go on to Step Three.
  • The BrowseClass documentation in the Application Handbook lists a Popup property described as a "browse popup menu reference." Reading the description of this property you discover that it is a reference to the PopupClass object used by the Browse, so you look at the PopupClass to see if there are any properties that will add an item to the popup menu for you. There are no PopupClass properties, so you go on to Step Four.
  • You look at the BrowseClass methods and don't see any that might apply, so you check the PopupClass methods, also, since you know the Browse object contains a reference to the PopupClass object. That's where you find the PopupClass's AddItemMimic method, which will add an item to the popup menu to press a BUTTON control. This appears to be exactly what you need for this task. Therefore, you now know that to add the Copy button to the popup menu for this BrowseBox, you can simply execute:

BRW1.Popup.AddItemMimic('Copy',?Copy)

!Call a PopupClass method through the

! Browse object's PopupClass reference

! property


So where does this one line of code need to go? Since the Popup property must already exist, it must come sometime after the BRW1 object has been initialized. And, since you just want to add this to the popup menu and not dynamically enable/disable it, this code needs to happen on the way into the procedure, before the user can do anything on the window. In the Embeditor, you can see that the BRW1.Init method is called in the ThisWindow.Init method, and you already know that ThisWindow.Init is always the first method called in any procedure with a window. Therefore, the best embed point to place this one line of code would be one near the end of the ThisWindow.Init method. You're done at Step Four!


Task: I want to call a Form directly from the menu (without a Browse) to add records.


This task actually implies the need to do three things:

  • alert the procedure that it will be inserting new records into the database,
  • ensure that any auto-increment keys are properly incremented, and

  • The Form Template's documentation in the Application Handbook doesn't show any template settings for this, so you go on to Step Two.
  • In the Embeditor, you see the only object in the Form procedure (other than two Toolbar-related objects) is ThisWindow/WindowManager. However, you also have a global object for each of the data files your application accesses. Therefore, a second candidate object/CLASS to look at for this task is Access:FileName/FileManager.

While exploring in the Embeditor you notice that the very beginning of ThisWindow.Init is where the value of the GlobalRequest variable (which tells the Form procedure what file action to perform) is obtained. You know you need to deal with this issue, so in the very first embed point available in ThisWindow.Init (immediately following the CODE statement) you add:


GlobalRequest = InsertRecord

!Set Form procedure to insert records mode

Having dealt with the first issue, you go on to Step Three.

  • None of the WindowManager properties listed in the Application Handbook appear to help for this task, so you check the FileManager properties also. None of these apply either, so you go on to Step Four.
  • The Application Handbook shows two FileManager methods (PrimeRecord and PrimeAutoInc) which appear to be exactly what you need for this task. Now you just need to find where to call them, so you go on to Step Five.
  • The Application Handbook shows a WindowManager method called PrimeFields that looks like the best place from which to call the FileManager methods. In the Embeditor, you write code like this into any embed point in ThisWindow.PrimeFields:

Access:FileName.PrimeAutoInc()

!Auto-increment key fields

Access:FileName.PrimeRecord(TRUE)

!Init other fields (suppressing clear)

Now, when you call this Form procedure directly from a menu (without an intermediate Browse) it will automatically be in Insert mode and will correctly handle auto-incrementing keys and setting up all the initial values you declared in the Data Dictionary. You're done at Step Five!


Task: I want to allow the end user to pause/resume report generation.

  • The Clarion ABC Templates have a "Pause Button" Control Template which does exactly this. Simply place this Control Template on the Report's ProgessWindow, and you're done at Step One!

Task: I want total fields on one tab and the BrowseBox which they total on another.


  • The BrowseBox Control Template documentation in the Application Handbook doesn't show any template settings for this, so you go on to Step Two.
  • In the Embeditor, you see that the only likely candidate object/CLASS is BRW1/BrowseClass, because it controls the browse list, then go on to Step Three.
  • The BrowseClass documentation in the Application Handbook lists an ActiveInvisible property which, when set to one (1), makes the Browse list active even though it's not visible. Therefore, you now know that you can simply execute:

       BRW1.ActiveInvisible = TRUE

       !Set the browse to always active


So where does this one line of code need to go? Since the ActiveInvisible property must already exist, it must come sometime after the BRW1 object has been initialized.  And, this property needs to be set on the way into the procedure, before the user can see the window so the totals will be calculated and displayed correctly.  In the Embeditor, you can see that the BRW1.Init method is called in the ThisWindow.Init method, and you already know that ThisWindow.Init is always the first method called in any procedure with a window.    Therefore, the best embed point to place this one line of code would be near the end of the ThisWindow.Init method. You're done at Step Three!

 

As you can see, this 5-step process leads you to the earliest possible solution to any programming task.


Are there any non-TopSpeed Clarion OOP Learning Resources?


  • The "Unofficial Clarion OOP Page" at http://www.incunabula.com has a number of articles specifically about writing OOP code in Clarion. This site is maintained by Dave Harms, co-author of Developing Clarion for Windows Applications, published by Sams.
  • The "Clarion OnLine" electronic magazine at http://www.clariononline.com (this requires a subscription) is published monthly by Online Publications (founded by Tom Moseley, co-author of Clarion for Windows for Dummies, published by IDG). This "e-zine" features articles of interest to all Clarion developers, including writing OOP code and using the ABC Templates and Library.
  • Many Clarion developers are on-line and willing to help others as needed. Your colleagues can be found in the TopSpeed forum on CompuServe (GO TOPSPEED) and in several Clarion-related internet newsgroups--most notably, comp.lang.clarion (on many new servers, including fortress.uccb.ns.ca) and topspeed.products.C5 (only on the tsnews.clarion.com news server).

Between these external resources, the information presented in this article, and all the Clarion documentation (both printed and electronic), you have a tremendous amount of information to help you learn how to use Clarion's ABC Templates and Library.  Keep studying and working with it and you will get to that essential "Ah Ha! That's what ABC/OOP is all about" bolt of enlightenment--probably sooner than you might think.  When you do, watch out! You'll not only own a copy of this Clarion "Formula 1 class" development tool, but you'll be driving it like a World-Class competitor--at TopSpeed!