![]() |
|
Published 1998-03-01 Printer-friendly version
Historically there has been a pronounced division in the ranks of Clarion developers - or so it has been alleged. At one end were the Designer users and at the other, the hand coders. Interesting, since for the first two years of Clarion's existence there was no Designer. So, at first, all Clarion users were hand coders.
With CFD3.0 and the introduction of open templates, the Application Generator became a full cycle tool and most of the previous reasons for avoiding Designer or for abandoning it midway through the development cycle became moot.
With C4, we face another paradigm shift, one that appears to dwarf anything in the history of the Clarion language. How this change is perceived is easily inferred from the wide variation in learning curves reported on the news group. This far into CFD and CW's deployment, most everyone had climbed the curve.
I, myself, am at the toe of the curve, but I think I've overcome the single most significant stumbling block. Objects are just another way of doing the things we've always done. The transition to object Clarion is no worse than the move from 2.1 to 3.0 or 3.0 to Clarion for Windows, and may even be easier. Moving to 3.0, we had to learn not only new language elements, but a new environment; we had to learn about embeds and about all the new routines. Moving to Windows, we had to adjust to major changes in buffer handling and all new embed names, among other things. Well, all of those routines and embeds we spent so long learning are still there. Certainly the routines are now methods and some of the embeds have moved about a bit. Mostly, they have new names and calling conventions ("That which we call a rose..."). In other words, I am not so anxious about objects any more. The stumbling block? Fear.
I suppose another way of looking at it is that an object is very much like a LIB, at least functionally. But, unlike a LIB, there is no black box, you can reach right into an object and change its behavior. And, you can "reach right into" it in several ways.
As I study the exchanges on the news groups, however, I see a new division developing; those who derive new class methods to accomplish what they want to accomplish versus those who continue to be Embeditors (coincidentally, also the name of the new two-way context sensitive source code editor).
Those who derive new class methods often have a background in OOP through exposure to another language. Others have taken the time to master the ABC implementation of OOP. Knowing how the objects supplied with Clarion behave, they naturally jump right into those objects to modify their behaviors (well, it seems natural to them). TopSpeed has, conveniently, provided a mechanism to do this that minimizes typing (it also minimizes the impact of changes in the base objects on upgrades, but that is just a serendipitous side effect, I am sure). On the other hand are developers (like me) who through force of habit or preference (or, like me, just plain laziness) naturally jump right into embeds to get the program behaviors they desire, just as they have for years.
What is remarkable about this difference in approach is that it is almost entirely spurious! It is a difference that is not a difference at all, it seems to me. These two groups accomplish exactly the same thing. By using different entry points in the IDE, they just do it in different ways. As David Hume said, different words, same object (grammatical, not programming).
There is, in fact, only one way to change the behavior of an ABC object, and that is to derive a new method. (Not entirely true, but close enough for the moment.)
That you are deriving new methods when you select the Classes tab, check Derive, name some new methods and their properties or override existing ones is quite obvious. What is less obvious is that you are doing exactly the same thing when you write embedded source code. Less obvious, but equally true (and, by the way, not only documented in "Easing into OOP," Chapter 2 of the Programmer's Guide but we do exactly this in the tutorial!).
Here's the wonder that Bayliss & Co., oops, The TopSpeed Institute has wrought: Embeds are actually points within virtual methods.
To understand what this means, first it is important to realize that generated procedures never use base objects. Objects in procedures are in fact derived from the base classes at run time. That is why, for example, you use "ThisWindow" in your code while the object source files use "WindowManager."
When you type code in an embed point, you are actually causing the App Gen to generate that code into a local method - a local virtual method. Methods whose code is generated from your embeds are referred to as "locally derived methods." If you check the generated source you will, in fact, find your embedded code in a Procedure (i.e., method) - a procedure that you will probably also find named in the ABC base class (which is why they need to be virtual; so that the base class can call methods in the derived object).
Local derivation also allows many legacy embeds to generate code into ABC methods simply by using two descriptors (names) for the same logical embed point. For example, one of my favorites,Format Queue Element, calls the parent ofSetQueueRecord after any assignments.
Is there a difference between naming a new method (as you would by deriving on the Classes tab, then typing the procedure's code in theLocal Procedures embed) and typing code into one of the standard embeds? Yes. Among other things, you tend to do a lot more typing the first way. But it seems to me that this difference is more logical than substantive, a difference of degree and not of kind, a difference of style. The only thing you can do from the Classes tab that cannot otherwise be done is to create new methods. (This could be the stuff of a great argument - the difference between creating a new method and creating a local routine. But not here.) As always in Clarion, it is a matter of which way you are comfortable doing it. The point remains, as I said, there is only one way to modify the behavior of an ABC object: derive.
Flames pertaining to the above opinion should be sent to: sparker@huitclose.jps.
Having said that, let's go right back to differentiating "derivers" and "embeditors" like any good Clarion user. (We veterans can go right on referring to "hand coders" and "Designer users." Most everyone will understand what we're talking about.)
Well, TopSpeed has finally laid claim to a paradigm. It is the browse-form paradigm. For many years, this claim has been made about Clarion. TopSpeed is now the one making the claim. However, there is a second paradigm hidden in the templates, a paradigm that is much more important to developers.
The browse-form paradigm is a user interface paradigm. As such, most of us have freely ignored it for years. We have named Source, Browse and, even, Report procedures as updates. We have started apps with Windows, Forms, Source or Processes. The other paradigm is the way procedures execute. So, it is rather more difficult to ignore. This is the Init-Ask-Kill paradigm. (Poser: If I-A-N is pronounced ee-ahn, then is I-A-K pronounced ee-k?)
Perhaps you've noticed (TopSpeed certainly has gone to some pains to point this out) that the entire executable section of an object Clarion procedure is one line:
GlobalResponse = ThisWindow.Run()
The rest of the procedure is called methods and their properties. (Ok, there may be a few Includes or Equates also...)
If you look at the Run methods (there are two), you will find a call toParent.Run in each of them. The code in the parent method forThisWindow.Run() is:
WindowManager.Run PROCEDURE
CODE
IF ~SELF.Init()
SELF.Ask
END
SELF.Kill
RETURN CHOOSE(SELF.Response=0,RequestCancelled,SELF.Response)
Init-Ask-Kill, eek.
Because of this paradigm shift, it is important to understand what these methods do, at least in general terms.
This is quite an easy method to understand.
If theInit method returns 0 (a/k/aLevel:Benign), theAsk method is called. Otherwise, Ask is not called. WhenAsk terminates,Kill is called and a value (typicallyRequestCompleted orRequestCancelled) is returned.
Also an easy method to comprehend,.Kill simply undoes whatever.Init did.
In broad outline, this method is also relatively easy to understand. It is the Acceptloop.
TheAsk method contains a call to theTakeEvent method.TakeEvent is where events are actually handled. For example,TakeFieldEvent, one of severalTake methods called byParent.TakeEvent, corresponds to the oldBefore andAfter Generated Code embeds for a control and using those embeds will actually generate into theTakeFieldEvent method.
TheAsk method is called only if: (1) theInit method completes and (2) the Dead property is not True. This implies that callingThisWindow.Kill or settingThisWindow.Dead = Trueimmediately beforeAsk (WindowManager Method Executable Code section ... Ask ... () and use Priority First) or duringTakeEvent (hence any of theTake methods it calls) terminates the procedure.
If you've run the application converter in manual mode, you know thatDo ProcedureReturn is gone, replaced byThisWindow.Kill(Do ProcedureReturn isn't actually replaced byThisWindow.Kill;ThisWindow.Kill is just the replacement recommended and used by the converter; this will be important in a minute). Now you know why it will work in any control's embeds or any time afterInit has completed.
This method is a little harder to understand. Harder because it does, in one go, several things, things that we are accustomed to having happen in several discrete steps.
The Application Handbook tell us that the Init method initializes a Window Manager object and returnsLevel:Benign if it completes successfully. Seems straightforward enough, doesn't it? And, if you stop reading there, it is.
But, if you place:
IF MESSAGE( ... ThisWindow.Kill END
into this method ... well, how fast can you say "GPF"?
Initializing a Window Manager object is much more involved than the opening statement in the Application Handbook implies. In broad brush strokes,Init:
Along the way, it also sets the first field to be selected when the windows displays and several other housekeeping tasks.
In other words,ThisWindow.Init does everything CW2 does between Setup Procedure and the beginning of the Accept loop.
So, the reason your Message() causes a GPF is that the window and files are not open at the default embed point, the WindowManager object isn't ready (developer created embeds default to Priority 4000 -- stay tuned on this subject). But, your call toThisWindow.Kill tries to close them and to Dispose of the WindowManager and FileManager objects. Tough to close what isn't open, especially a window, orDispose of what has not been properly instantiated; there are certain things that Windows just cannot forgive.
On the other hand, if you try to setReturnValue to, say,Level:Fatal orThisWindow.Request toRequestCompleted before the parent call at priority 5050, the rest of the procedure will still execute; your assignment will be overwritten on return from the call to the parent method.
So, we have no choice but to derive. And we know a bit about the objects effected. Great. Now, where is the embed ...?
Before beginning a Process or a Report, I almost always use the Message() function to ask the user whether they are sure they want to proceed. There are times I do this before entering browses and forms too. If they answer "No," I terminate the procedure. I also tend to call procedures to capture range limit data like dates, policy numbers and other ID's. If you check authorization codes, this is likely where you do it.

Well, theSetup embed that I used to use is still there. In fact it generates intoWindow Manager, Executable Code Section ... Init ... (),Byte (see Figure 1), pretty much where we would expect it to be based on the previous discussion. But, its default priority is 4000, well before the call to the parent method and the opening of the files and the opening of the window and the initialization of the required WindowManager and FileManager objects (one FileManager object for each file in the procedure). So, if the user answers "No" or Cancels from the capture form, GPF city.
The solution is typically Clarion in that there isn't a single solution, but two. Either you can move the code to a priority level after Init is (substantially) done or you can eliminate the call to.Kill entirely.
If you change the priority, most developers on the news group report using Priority Last successfully (see Figure 2). But, any priority after 8800 (according to DAB) will do the job. The important point is that at this place in the code,Init has completed its essential tasks, so .Kill is safe.

Alternately, you can change your code at the default Priority to:
IF MESSAGE(... RETURN(Level:Fatal) END
which will have the same effect. "Return" terminates the current method. "Level:Fatal" is a value that other methods are programmed not to accept, so they do not execute.
There is an application which you may download (the button is at the top of this article) demonstrating all this and a bit more.
It demonstrates canceling a Process. One procedure presents a single MESSAGE() and the other presents two (similar to aMESSAGE() and a data collection window). The first uses an early priority and the second,Priority Last.
Note that my use of a Process is incidental. One of the beauties of OOP in TopSpeed's implementation is that the same Window Manager is used by all procedures with a window. So, techniques that work in a Process will also work in a Report (which is now just a Process with aPRINT() statement and a Report structure), a Browse or a Form completely without modification. That is kind of cool.
The app also shows where and how to filter records using an embed and where and how to perform an action on each (unfiltered) record. It also demonstrates reporting results at the end of the Process, but only if you did not abort (see Figure 3).

One thing you will need to do is supply your own dictionary and change the file references so they are appropriate to your DCT. This way, you can test the app against your own data.
For several years, we have had Before and After embeds:Before Event Handling,After Generated Code, etc.
Priorities give us a new set of places for code: During.
There is nothing mysterious here. Priorities are just arbitrary numbers indicating the desired relative position of a piece of code. In fact, most methods use only a limited range of Priorities (usually 5000). The Init method and a few others rely more heavily on this scheme. To capitalize on it, you really only need to open the Embeditor and check the relative placement of your code.
Twelve moths ago, I entered a whole new world of Clarion: tsnews.clarion.com, the news groups for C4 and CWIC.
There I have met, gotten to know and been helped by a number of top flight Clarion developers. There too, I learned that for many years, I have not known the correct meanings of three very common words. "Holiday" I now know really means "a day on which you work in your pajamas" (i.e., from home). "Sleep" refers to the period between midnight Eastern time and about 6:00 A.M. GMT. And, "vacation" is the time when you catch up on customer projects.
Jim D., Arnor B., Mike P., Jim K. and all the others who have been so free with their time, their information and their talents, thank you.
Copyright © 1999-2008 by CoveComm Inc. All Rights Reserved. Reproduction in any form without the express written consent of CoveComm Inc., except as described in the subscription agreement, is prohibited.
Clarion Magazine ISSN 1718-9942
One year: $189
(includes all back issues since '99)
Renewals from $139
Two years: $289
Renewals from $239