Programming Objects in Clarion. 2nd Edition
Programming Objects in Clarion. 2nd Edition
by Russell B. Eggen
Programming Objects in Clarion
by Russell B. Eggen
Published by:
CoveComm Inc.
1036 McMillan Ave
Winnipeg, MB R3M 0V8
All rights reserved. No part of this book may be reproduced or transmitted in any form by any means,
electronic, mechanical, photocopying, recording, or otherwise, without the prior written permission of the
publisher. For information on getting permission for reprints or excerpts, contact [email protected].
Includes index.
ISBN 0-9689553-2-0
The information in this book, and any source code and/or information in downloads referenced in this book,
is distributed on as “As Is” basis, without warranty. While every precaution has been taken in the preparation
of this book, neither the editor nor the publisher assumes responsibility for errors or omissions in the book, or
in the instructions and/or source code in downloads referenced in this book.
Clarion™ is a trademark of SoftVelocity, Inc. This and other trademarks which may appear in the book are
used in an editorial fashion only and to the benefit of the trademark owner, with no intention of infringement
of the trademark.
Printing History
Published by:
CoveComm Inc.
1036 McMillan Ave
Winnipeg, MB R3M 0V8
T. 204.943.5165
F. 204.477-4518
[email protected]
Table of Contents
Foreword i
Acknowledgements iii
Index 241
FOREWORD
Whether you are brand new to Clarion, or an experienced developer since the
"Version 1.0" days, or somewhere in between, you've probably heard of Russ
Eggen.
First, try exploring the public newsgroups. No matter what the topic may be, if it
is remotely related to Clarion, Russ will be there. If you ever have an opportunity
to visit any of the scheduled online chat forums, Russ will be there. If there is a
developer's conference scheduled in your area, chances are that you will meet
Russ.
I have always thought that there is a "Team Eggen" somewhere; 10 college interns
who secretly work for Russ by writing and posting and answering his many
messages and responding to comments on his articles. But no, it is just one man
behind all of this effort.
My point to all of this is that you will realize as you read this book that Russ has a
refreshing and unstoppable passion for Clarion. You will immediately see that he
really enjoys his work, which in part is writing programs and solving business
problems using Clarion. More importantly, he likes to share those binary battles
and software conquests with all Clarion developers.
The migration from writing procedure-based code to the object oriented approach
can be a dry and tedious journey for many. What Russ has done with this book is
to upgrade your journey to first-class. His passion and love for what he is doing is
clearly reflected in his writing, and the time you spend reading these pages will be
well worth the investment. After reading this book, I hope that you find yourself
i
secretly inducted into "Team Eggen." As the membership of this team continues to
increase, it will certainly speak well for the future and prosperity of all Clarion
developers. Thanks again, Russ!
Bob Foreman
SoftVelocity Inc.
ii
ACKNOWLEDGEMENTS
During the writing of any book, an author is assisted by many people. This book
is no exception. I wish to express my thanks to the following people:
Stephen Bottomley for providing the example for the pie interface.
Andrew Guidroz for the questions about the ErrorClass, thus bringing about the
idea on how to trap specific error codes with the ErrorClass.
Grateful acknowledgment goes to Andrew Ireland. If it weren't for his coding
disciplines, my code would be harder to read, let alone maintain. Also, his simple
ideas lead to simple solutions, regardless of the complexities of the problem
addressed. After all, what are friends for?
I would be remiss without expressing some thanks (in no particular order) to
Mark Goldberg, Dave Harms, Tom Hebenstreit and Mike Gould for their
assistance, comments and encouragement.
Special thanks to Robert Healey for the swell graphics. If not for his efforts, you
would have to suffer my horrible stick figures.
To my wife, Kathleen, for feeding me, offering encouragement ("that's nice, dear"
and pats on the back), proofreading (catching those typos), ordering me to get
some sleep once in a while, and just putting up with me.
And most of all, my thanks goes to the Clarion community. They are constantly
curious, willing to learn and not afraid to ask questions in the quest to greater
understanding. I dedicate this book to them to them.
And last, but certainly not least, the fine crew at Softvelocity. Without their tireless
(and often thankless) actions, Clarion might not be in existence today.
Russ Eggen
www.radfusion.com
iii
iv
Chapter 1
STRUGGLING WITH OBJECTS
When I was first asked to present the topic of Object Oriented Programming at
the East Tennessee Conference in May of 2002 (also known as ETC III), I decided
to ask the Clarion community what they perceived to be the problem with
working with objects. I am glad I asked, as the answers were not what I was
expecting. These answers did paint a picture, but not just any picture. And when
someone answered one of my questions about OOP, I asked myself, “Why did
they say that?”
My investigation revealed something that I had never really considered. Despite
the many good articles, books, documentation and newsgroup threads available to
Clarion developers, many still found dealing with objects difficult!
I discovered a few reasons why this is so. Some developers said that their
applications were too far into the development cycle to make the switch to ABC
(the OOP based templates). Other developers were still thinking in linear, instead
of object-oriented, terms when writing code, and when trying to write ABC code.
ABC has its critics, and some of their points are valid. These criticsms often made
a good enough excuse not to use ABC. And Clarion developers have put a lot of
“sweat equity” into learning the non-OOP Clarion (sometimes called “Legacy”)
template set. That learning curve was not exactly easy as these templates produce
a lot of excess code. Clarion programs were fat with unneeded code and it took
time to wade through it. The original Clarion templates (I don't know how to be
diplomatic about this) are awful. I could state many arguments supporting that
claim, but I am drifting off topic.
1
Programmi ng Objects In Clari on
In response to concerns about the new object-oriented code, the designers of ABC
told everyone, “Just never mind about this new code we’re generating!” Now, that
was not a clever idea, despite the best efforts of Topspeed staff (of which I was a
member). What I mean by that is that if you are to upset accepted ideas, you had
better be prepared to replace them. On the other hand, the education department
certainly put their best efforts in this, and so did those in documentation, but
there were still a lot of worries about what that ABC code really meant.
During the Topspeed days, you may recall a group of talented developers known
as Team Topspeed (TTS). These people knew Clarion very well, and each member
was a specialist in some area. Moreover, they were quite willing to share their
knowledge with others.
I am going to let the cat out of the bag here, but TTS also struggled with ABC in
the many beta stages. They objected on many of the same issues that later
developers would struggle. They were quite willing to learn, but they did raise
some legitimate concerns.
For example, one TTS member made a very good point. When discussing the
issue of code re-use, his rebuttal was “So what? I use routines for that. Show me
why OOP is better.” Another member questioned the wisdom of naming a report
object “ThisWindow”. The small People application was the result of that
conversation.
The whole point of this is simply to say that others went through the pain of
shifting their style of coding too. I include myself in this group. I learned the
procedural, top- down style of programming before I learned OOP. Procedural
code works. It can be very difficult indeed to completely change a way of
programming, let alone give up what works.
During my years as a Topspeed instructor, I had the honor (or was that sheer
terror?) of teaching the very first ABC class. If that was not bad enough, ABC was
in beta version 3 (out of 6) at the time. For those that were around, beta 3 was a
complete re-write as beta 2 was unworkable. I was nowhere near the TS offices
where I could easily get some help.
There I was, standing in front of about 10 students, all of them waiting to see and
hear about this new “silver bullet” called ABC. I was under no pressure at all!
Looking back on that experience, it was rough, but I think I got most folks
through the difficult bits.
In later classes, I kept revising the OOP lessons based on surveys students filled
out at the end of class, until I got to the point where I taught one class and three of
2
Struggling wi th Objects
those students were just amazed at how easy ABC really was. That was a nice
thing for them to say, but I was aiming higher.
“Could you apply this information in your current projects?” I asked.
“Absolutely!” they said. I grinned and informed them that within hours they were
going to prove this point (I had some lab exercises waiting). And they proved they
could apply the materials I presented.
I knew then that I could deliver the theory of ABC, and that the theory, when
applied, worked. This book is the result of those experiences.
Now let me explain how all of this flows.
3
Programmi ng Objects In Clari on
If you design and code any object well, you will do it once, but you can use that
code everywhere, greatly reducing your future workload.
That price does not sound so bad now!
In live class room settings I've discovered that the proper sequence of topics starts
with the basics, and moves the student through each successive topic, building
knowledge and skills. Thus, the sequence of study in this book is as follows:
• Object Oriented Programming
• ABC templates
• Converting existing code to objects
• Coding template wrappers for those objects.
4
Struggling wi th Objects
Chapter 4 examines the ABC class library. Once you understand what ABC is
doing from an OOP viewpoint, finding embeds is easy, and in the worst case, still
easier than you think.
Template Wrappers
Chapter 6 covers how to write template wrappers for objects you create. This
builds on the previous chapter. You will have template wrappers for the relation
tree class. And finally, Chapter 7 explains in detail how to use the template you
created in Chapter 6.
Now that that is out of the way, let me continue with the theory of OOP, which
was the sole topic in my presentation at ETC III.
5
Programmi ng Objects In Clari on
6
Chapter 2
THE THEORY OF OOP
In every subject one studies, there must be a basic, simple starting point. Let me
get this part out of the way right now so I can get into the “magic”.
OOP is spelled O-O-P.
Did everyone get that? Excellent! Now, I will build on this concept.
OOP is not POO spelled backwards. How does that seem for everyone? Is anyone
lost?
Am I going too fast? Is everyone having fun? No one is unconscious and everyone
is wide-awake? Then I will proceed.
OOP defined
When I study something new, I must have a list of keywords with workable
definitions in order to learn that subject. Imagine my disgust when I visited many
bookstores to find a good book on OOP and I did not find one - that is correct,
not a single, really good book. I found rows and rows of books on the subject of
OOP. Some used C++ for example code; others used Delphi or some other
language. All of them I rejected. Not because there were no Clarion examples, but
because all but two of these authors did not even attempt to define OOP!
Moreover, the two authors that attempted definitions came up lacking. Here is one
such example:
7
Programmi ng Objects In Clari on
“OOP deals with objects that have certain characteristics such as <buzzword>”,
Why do I think that is lacking? That definition can’t work! What do I mean by
that? You cannot apply this definition, no matter how bright you happen to be.
Can you even use it in a sentence that makes sense? All right, enough of my pet
peeve, I will not keep you waiting any longer.
OOP is an acronym meaning Object Oriented Programming.
You are sitting there thinking, “I knew that, so what?” Defining acronyms is not
helpful if one does not know the words of the acronym. However, if we get the
definitions of these words, perhaps an understanding of “what is it?” will become
visible. For those who have seen these definitions before, it will not hurt to revisit
them.
Object - [n] 1) Anything that can be apprehended intellectually. What object do you
think of when I say that? Do you have an object that starts with the letter E? What object
best represents success? 2) Anything that is visible. What is that object in your mouth?
Have you ever seen an unidentified flying object?
Orient (ed) [vt] To point or direct at something or in a direction. If you would
orient your gaze to the south end of the runway, you see the shuttle is about to land
Program (ing) [v] The act of writing computer code to perform a certain task. The
project is now in phase two of programming.
Combine these three words and you have another definition. Object Oriented
Programming means the “writing of code directed or aimed at objects”.
I don’t mean to break your train of thought, but believe it or not, you’ve just
studied an area that many people cannot explain. So for this exercise, simply
define “OOP” in your own words. Refer to the above example if you need to. And
finally, use “OOP” in a few sentences of your own creation, showing the above
meaning (seriously). Do this until you feel comfortable with it. Some people need
to do about 10 or more sentences before they get comfortable. Some require less.
Once you have done this, please proceed.
Now you may find yourself asking, “Where do I start?”
8
The Theory of OOP
I think a more precise question would be, “How does Clarion do OOP?” Get that
question answered and I think you have your starting point. To be more precise,
since the subject is really “objects”, what is the meaning of that? Let me use a real,
yet over-simplified example.
In a business application, you deal with data files. There are files of all shapes,
sizes and types. If you were to design a file object, what would it do? One cannot
get very far without opening a file, so it must do that. In addition, it must close a
file, so add that to the list. You should be getting an idea of what a file object
should do. There is more to files than opening and closing them, but I will leave it
at that for now.
You should notice one thing I did not mention. Which file am I talking about? In
a procedural world, you must name the file you wish to open and close. In the
OOP world, you do not (at least at design time). This means that you only need to
think about an object that will work the same on any file. This is what others
mean when they tell you to think of abstract data. Abstract data does not have
clear borders; it is a broad brush stroke, like the file object.
What you do is gather all the attributes and behaviors that would apply to a file -
any file. At this point, the definition of the object looks incomplete; by itself, it
really can’t do anything useful. That is correct as you do not want the object doing
everything or even knowing what the file name is. You supply the file name at
runtime.
I will explain a little later how an object becomes useful at runtime. . First, you
need to define what actions and attributes that apply to a given file. You place
these into a container or bucket.
That brings me to the first OOP keyword.
Encapsulation
9
Programmi ng Objects In Clari on
does not depend on anything outside itself. Alternatively, you could call this
collection of code and data an object. What does this really buy you? It allows you
to address this “grouping” as a single thing or entity.
The following sentence is a simple example of what I mean by that: “I’ll never trust
him again as he is a Judas.” You probably know what I mean by the word “Judas”.
I am referring to the Biblical story of the betrayal of Jesus by a disciple named
Judas. Thus, the word “Judas” encapsulates the betrayal of Jesus and how this
happened. If “Judas” were not encapsulated as a concept, you would need other,
and probably many more words to describe why a person is not trustworthy, what
did they do to earn that reputation, etc.
If you were to code a cat, you want the cat code distinct and different from a dog.
You also do not want the dog bits mixing in with the cat bits. I am getting a little
ahead of myself here, but I wanted you to get an easy image in your head without
getting too technical at this point. You can see that if you wanted to code a cat,
you want nothing to do with anything that is “non-cat”. Thus, you enclose the cat
code in its own capsule.
Encapsulation is where the concept of a class comes in.
The above is a simple class definition. Notice I said “definition”. This means it
belongs in the data section of your program. Class definitions belong in the same
section with variables, WINDOW, FILE, VIEW, REPORT and other data definitions. The
data section in Clarion is anything after PROGRAM (or MEMBER or PROCEDURE) and
before the CODE statement.
10
The Theory of OOP
Think of a CLASS as an enclosed MAP statement, but with data definitions as well.
The above CLASS deals with the opening and closing of a file. There is more to a
good file class than that, but I wish to keep these concepts simple for the moment.
There is one thing that may not be obvious to you by looking at this definition.
The name of the CLASS is FileClass not CustomerFileClass. You do not wish to
repeat the same code for every different file. The old Clarion templates produce
code in this manner. Why code the same thing repeatedly using a different label? I
do not know about you, but I would get bored. There is more to say about code
re-use, which I will discuss later. First, I need to introduce some new buzzwords
into your vocabulary.
Property - defined
If you notice, there are two data types in the above CLASS structure. These are
properties to use the OOP lingo. Properties describe the attributes of an object.
How big is it? What color or size is it? This is like describing yourself to someone
you never met.
Method - defined
There are two procedures in this definition as well. A method is the term for these
procedures. That is what the CLASS does.
When you describe an object with code, you declare a CLASS structure. Then list
the properties and methods in it. A CLASS therefore, encapsulates methods and
properties. Taken together this is the beginning of an object. What do I mean by
saying “beginning”?
Declaring a CLASS is not always declaring an object. A CLASS is a definition of an
object, but doesn’t exist as something that can be used. To use a class, you have to
create an instance of it, called an object. This is why Clarion uses the keyword
CLASS and not the keyword OBJECT. A CLASS definition does not always bring an
object into existence. This brings me to the next keyword in the OOP vocabulary.
Instantiation
11
Programmi ng Objects In Clari on
Here is the key to instantiation: You may have more than one instance of a CLASS
declaration. This means you define the CLASS once and instantiate it multiple
times. The advantage of this should start coming into focus now. Simply put,
define once, then instantiate the objects that you need from that definition.
For example, I declare a FileClass. However, I instantiate a CustomerFileClass
based on the FileClass definition. I will not and do not need to declare a clone of
the FileClass to use it on the Customer file.
2) A simple data declaration statement with the data type being the label
of previously declared class structure, instantiates an object of the same
type as the previous class.
3) Declaring a reference to the label of a previously declared class
structure, then using NEW and DISPOSE instantiates and destroys an
object respectively.
Global objects
A class declared in the global data section is instantiated for you automatically at
the CODE statement that marks the beginning of your program. When you RETURN
to the operating system, the object is destroyed for you. This means that visibility
and lifetime of such objects are global. The code example below demonstrates the
three ways of instantiation.
12
The Theory of OOP
CODE
!MyClass and ClassA automagically instantiated
ClassB &= NEW(MyClass) !Explicitly Instantiate object
! some code here
DISPOSE(ClassB) !Destroy object (required)
RETURN !MyClass and ClassA
! automatically destroyed
Modular objects
A class declared in the modular data section is instantiated for you automatically
at the CODE statement that marks the beginning of your program. When you
RETURN to the operating system, the object is destroyed for you.
This means that the lifetime of module objects is global, but their visibility (or
scope) is limited to the module.
13
Programmi ng Objects In Clari on
CODE
!MyClass and ClassA automagically instantiated
ClassB &= NEW MyClass !Explicitly Instantiate object
! some code here
DISPOSE(ClassB) !Destroy object (required)
RETURN !MyClass and ClassA automagically
!destroyed
Local objects
Objects declared in a procedure’s data section are instantiated for you
automatically at the CODE statement that marks the beginning of the procedure’s
executable code, and are automatically destroyed for you when you RETURN from
the PROCEDURE. This limits their lifetime and visibility to the PROCEDURE within
which they are declared.
SomeProc PROCEDURE !Local Data and Code
CODE
!MyClass and ClassA automagically instantiated
ClassB &= NEW(MyClass) !Instantiate ClassB object
! execute some code
DISPOSE(ClassB) !Destroy ClassB object (required)
RETURN !MyClass and ClassA automagically
! destroyed
These scope and visiblity issues should be straightforward. The point in bringing
this up is that a CLASS is treated the same as simple variable declarations
14
The Theory of OOP
concerning when they are available and what code may access them. This also
means that if you try to use an object’s methods or properties when they are not in
scope, you will get errors.
Dot syntax
Dot syntax, also called “field qualification syntax,” is simply a way of telling which
object owns which property or method. It follows the same premise as using the
prefix on labels in your code. One difference is you use a period instead of the
colon.
In other words, you may have two or more class structures with methods called
Init. The way to distinguish them is by prepending the object name, followed by a
period and then the method name. The same is true for properties.
Think of it like an English sentence. You first read the noun, then a verb or some
other modifier to complete the sentence. Granted, they are short sentences, but
you can get the idea.
Fred CLASS
Pay DECIMAL(7.2)
Hours LONG
Work PROCEDURE(LONG),DECIMAL
END
If you use the above class in your code, you might write some code like this:
CODE
FRED.Pay = FRED.Work(FRED.Hours)
You could surmise, without seeing all the source, that the code is working on
Fred’s pay.
15
Programmi ng Objects In Clari on
The rule is simple: when referring to any method or property from any class, use
dot syntax notation.
However, you may notice there is a problem with the code. It will only calculate
Fred’s pay. If your name is not Fred, you would be upset getting no pay.
Here is where the procedural style coders fall down in their approach to object
oriented coding. They would write the same code for each of their employees.
Eventually, you would have code that works. If it works, then why fix it? But
assuming that the code always correctly calculates Fred’s pay, would it not make
sense to re-use the above code? However, the object’s name is Fred, how do you
do this? I suppose you could name it Employee instead of Fred, which makes
sense. However, which employee is paid? Not much progress really. I will explain
this after a short detour.
16
The Theory of OOP
Figure 2-1 illustrates where the different sections belong in your source file.
There is another way of coding methods using the INCLUDE statement, but that is
covered later.
Therefore, the source where the Fred class is used would look like this:
! PROGRAM or MEMBER or PROCEDURE
Granted, you need more than the above to have a working Clarion procedure that
makes use of a CLASS. The concept is what is important at this stage. A
demonstration of working code comes later.
17
Programmi ng Objects In Clari on
As I explained earlier, each class method is declared using the class name,
followed by a period, followed by the method name. In this case, the class is
called Fred. In the main body of class code, you use that label. However, in the
code for the method, there is no way to know which object is executing. This is
because you may have many instances of this object in use. In addition, I am
giving you the restriction to code this only once, so you cannot refer directly to
the class itself in the class’s own code. You will soon see this is not a restriction at
all.
SELF
Instead of using the label of the class in the method code (as opposed to the main
body of code), you use the label SELF. The definition of SELF is “whatever the
current object is.” You can see this in the following code listing:
PROGRAM
CODE
Fred.Pay = Fred.Work(Fred.Hours) !Code outside “Fred” object
Using the above code, SELF is really Fred as Fred is the object name in the main
body of code. Another instance of the class exists, with the label Barney; in that
instance, SELF is Barney. The key concept to get from this is that for both Fred
and Barney, you execute the same code! You are not executing a copy of the class;
you are not executing a clone of the class. You are executing separate instances of
the Employee.CalcPay method. You write the code once, but use it many times
for different objects.
This is something you cannot do with routines. Routines act on one piece of data
and that is it. If you need to do the same thing on another entity or in another
18
The Theory of OOP
procedure, and you find yourself coding yet another routine, it’s time to think
about a class instead.
Let me give you a conceptual example of what I mean.
19
Programmi ng Objects In Clari on
MicroWave CLASS,TYPE
Power LONG
Time LONG
Cook PROCEDURE(LONG xPower, LONG xTime),LONG,PROC
END
Nachos MicroWave
Reheat MicroWave
Power:High EQUATE(10)
Power:Low EQUATE(l)
CODE
SELF.Power = xPower
SELF.Time = xTime
LOOP Counter = 1 TO SELF.Time
!Cooking code here.
END
RETURN True
You can see that both the Nachos and Reheat objects are Microwave data types
and these objects are instantiated. In the main body of code, I used both objects,
but if you notice, there is only one procedure labeled Cook. This is because I used
the same microwave to cook my nachos and to reheat something.
Are you starting to see the picture of what is going on here? So far, I have covered
encapsulation and instantiation. These are powerful, yet simple concepts. There is
another benefit to these concepts before I move on.
If you notice, I have the Cook method coded in one place. Yet if my nachos burn
or my coffee is cold, I have a bug in my code. Now ask yourself, how many places
do you have to look to find the bug?
20
The Theory of OOP
Coding OOP style not only allows the writing of less code, but it also reduces your
maintenance. When you do need to fix something, you only have to look in one
place - you don’t have to hunt around for additional copies of the same code.
These are two more keywords in the OOP lexicon, and they describe special
methods in a CLASS. The constructor is a method automatically called when an
object is instantiated. It does not matter how the object is instantiated. The
destructor is a method automatically called when you destroy an object, again
regardless of how the object is destroyed.
Their use is very simple. Declare a method named Construct with no parameters
in your class. Do the same with the destructor except name it Destruct. You may
use one, the other, neither, or both. Their use is totally up to you.
21
Programmi ng Objects In Clari on
worry you too much at this point). Here’s what that class would look like with
automatic constructor and destructor methods:
FileClass CLASS,TYPE
Name CSTRING(255)
Path CSTRING(255)
WorkQ &TypeQue !TypeQue definition not shown
Open PROCEDURE
Close PROCEDURE
Construct PROCEDURE
Destruct PROCEDURE
END
!Main body of code here
FileClass.Construct PROCEDURE
CODE
SELF.WorkQ &= NEW(TypeQue)
? ASSERT(SELF.WorkQ &= NULL, ‘Cannot reference WorkQ’)
FileClass.Destruct PROCEDURE
CODE
IF ~SELF.WorkQ &= NULL
FREE(SELF.WorkQ)
DISPOSE(SELF.WorkQ)
END
Looking over the above code listing, it shows a simple declaration for the
constructor and destructor in the class declaration. Below the comment indicating
where normally you see the main code, are the two methods. In this case, you see
a reference assignment of a newly created QUEUE to a QUEUE reference variable. The
ASSERT is debug code that will show the assert message if the condition is false
(the assertion being that the condition is always true). The point here is that this
code executes every time this class is instantiated.
The destructor “undoes” what the constructor built. If the reference is not NULL,
it FREEs the QUEUE, then DISPOSEs the QUEUE. This is a good reason to use
constructors and destructors. A destructor can “clean up” what a constructor did.
I will revisit constructors and destructors a little later. For now, I need to add a bit
more to encapsulation.
22
The Theory of OOP
Extreme Encapsulation
You recall that a class encapsulates data contained in it. This could imply that the
class hides data from the outside world. In one sense, this is a good thing, as you
certainly do not want anything from the outside to step on data and corrupt it.
Encapsulation can be a guarantee that the data is in a reliable state.
Clarion supports this with the PRIVATE attribute. When used as an attribute in a
property or method declaration in a class, only other members of that class may
access that property or method. In other words, PRIVATE hides data and/or
methods from everything outside the class.
MyClass CLASS
MyProperty LONG,PRIVATE !private Property
Method PROCEDURE
MyMethod PROCEDURE,PRIVATE !private method
END
CODE
MyClass.MyMethod !Invalid here
MyClass.Method !Valid here
MyClass.MyProperty = 10 !Invalid here
MyClass.Method PROCEDURE
CODE
SELF.MyMethod !Valid here
SELF.MyProperty = 10 !Valid here
23
Programmi ng Objects In Clari on
You know that inheritance means you receive a vast sum of money after a rich
relative died. OK, that does not seem to apply to Clarion, let alone OOP.
Inheritance (n) - something received from a predecessor, as a trait, or
characteristic. He inherited his musical abilities from his father.
Inheritance is similar to another common OOP term: derive.
Derive (v): To convey from one (treated as a source) to another, as by
transmission, descent, etc.; to transmit, impart, communicate, pass on, hand on.
You derived your sense of humor from Uncle Harry. He derived his knowledge from
hard study.
This is where OOP can get a bit tricky, especially to those trying to grasp it for the
first time. To derive a new class, you simply name the parent class as the
parameter to the new class statement. The new class declaration inherits
everything from the parent class. The difference here is that, in OOP no one has to
die for the child to inherit.
This is a good opportunity to introduce some more OOP terms: Base class and
derived class.
A base class is a class that has no parameter to its class statement, meaning it does
not inherit anything. However, a derived class always has a parameter naming its
parent class. Notice that I did not say that the parameter to the derived class
statement names a base class – it does not always do this. The parameter to a
derived class statement names its parent class, which could be either a base class
or another derived class. This means that you can have multiple generations of
inheritance.
Let me illustrate this with the FileClass.
24
The Theory of OOP
FileClass CLASS,TYPE
Name CSTRING(255)
Path CSTRING(255)
WorkQ &TypeQue !TypeQue definition is not shown
Open PROCEDURE
Close PROCEDURE
Construct PROCEDURE
Destruct PROCEDURE
END
CustomerClass CLASS(FileClass) !Derived class
!Contents of the class structure here.
END
CODE
!Some code to point to the customer file
CustomerClass.Open
Extending a class
In the above code listing, CustomerClass inherits everything from FileClass.
When you derive a new class, the purpose of doing so is usually to extend the
class. In other words, you want all of the functionality of the parent, but you want
to embellish existing code or add new code. With inheritance you can do all that
without changing the original definition, and without rewriting the same code you
already have that works.
That is a very good thing to do and makes maintenance easier. Let us say you have
a VendorFile class derived from the FileClass too. If opening the Vendor file
works, but opening the Customer file goes “bang”, you can safely assume the base
FileClass is fine. That must mean there is a bug in the CustomerClass. Now
you know where to look. So, that is a huge benefit.
This should indicate to you how to design a set of classes. Make a base class that
has everything in common about the type of object you need. For example, code a
file class that is as general as possible. This class has everything that you may need
to handle only one file. By that, you do not yet know which file, let alone the
driver type.
As I touched upon before, you need to open a file, and close it. This is a common
thing to do for any file. I would strongly suggest that you keep such designs very
simple. By that, I mean so simple it hurts. Have you noticed I have said nothing
about checking for errors or handling relations? Those are general in nature as
well.
25
Programmi ng Objects In Clari on
An ErrorClass has broader applications than looking for file errors. Therefore,
that would be another class. A RelationClass could be complex, but that could
have a FileClass as its parent. In this case, the RelationClass derives
everything it knows about files from its parent.
Derivation and inheritance are powerful, yet simple concepts in OOP. Those
seeing classes derived from parents for the first time can get confused. This is
because the code does not appear to be complete.
Overriding a class
Another thing you can do with inheritance is override parent methods. This is
very simple to do. All you need to do is declare a method with the same label and
prototype as the parent method. This means that you want to toss out the parent’s
method and code everything in the derived class. That gives a first impression that
it is double work.
Why would you do this? The parent method may not be applicable for what you
need to do. In this case, simply write the replacement code. You can combine both
methods if you wish, and I will explain how to do that shortly.
Multiple Inheritance
Clarion does not support multiple inheritance. You may only derive from a single
parent. Other languages such as C++ do support this concept, but this does not
mean Clarion is crippled. Multiple inheritance introduces ambiguity into the
code, and this is not only hard on the compiler, but the programmer.
If Clarion supported this, you would have to code disambiguate methods to
explain to the compiler which objects derive from where. That is not a good use of
programmer time.
Composition
Where multiple inheritance would be useful, Clarion uses a simple way of giving
you the advantages of multiple inheritance, but without the ambiguity.
Composition is the term for this technique.
Composition - In a child class, the data type of a property is a reference to
another class.
26
The Theory of OOP
Composition gives you all the benefits of inheritance too. The main difference is
that you do not have to write any code to disambiguate what you mean. One line
of code is all you ever need. Let me illustrate this with an example:
ApplePie CLASS,TYPE !Declare Base Class
Apples STRING(20)
Crust STRING(20)
Bake PROCEDURE
END
You can see from the above listing the AlaMode class derives from the ApplePie
class. You can tell this as ApplePie is a parameter in the class statement, thus it is
a child class. However, look at the OnTheSide property. It is a reference to the
IceCream class.
Both inherit everything from their parents. The reference to IceCream is the same
thing as declaring the IceCream class inside of the AlaMode class. It is also the
functional equivalent of listing more than one parent as a parameter of the class
statement. However, the reference is the legal way to do this, and keeps your code
clean.
Moderate Encapsulation
Recall that a method or property with a PRIVATE attribute is for the exclusive use
inside the class. Absent of that attribute, methods and properties are “public”,
meaning you may access them from outside the class. Sometimes, you do not
want either.
Use the PROTECTED attribute when you need to restrict methods and properties to
the class they belong and any derived classes that may need them.
27
Programmi ng Objects In Clari on
You may wish to use this attribute when data needs some protection from outside
the class, but must also be available to child classes.
MyClass CLASS !Declare Base Class
Property LONG
MyProperty LONG,PROTECTED !Semi-Private
Method PROCEDURE
END
ClassA.AMethod PROCEDURE
CODE
SELF.MyProperty = 1 !OK within method of class
Overriding
28
The Theory of OOP
You can see the Bake method in the ApplePie class. That makes sense, as you
need to bake apple pies. Look at the Grandmas class. It also has a Bake method.
Compare the two methods, and you will see they both have the same label and the
same prototype.
Thus, the derived Grandmas class overrides the ApplePie method. This means
that for whatever reason, the parent method may not be applicable to the child
class. Thus, you write the appropriate code.
An ApplePie.Bake method may be fine for apple pies in general. In addition, that
method is fine for Dutch and American pies. However, Grandmas may be a special
recipe where a special bake method is required.
There is more to overriding methods than this, but I am leaving it here for now.
I mentioned previously that there is more to tell you about automatic constructors
and destructors. Well, now is a good time to tell you about how inheritance affects
constructors and destructors.
What happens when a parent class has a Construct method and the derived class
needs one? I already covered overriding methods in derived classes, so you might
guess that the derived class Construct method would simply override the parent
class method. That guess would be wrong, of course.
So why is it wrong? Because overriding the parent class constructor might mean
that code that is required to initialize inherited properties might not execute. If the
29
Programmi ng Objects In Clari on
inherited parent class properties failed to initialize, you might end up with
unexpected behavior in your derived class. Therefore, automatic overrides do not
happen with constructors. Instead, by default, they all execute in the order of
object instantiation.
First, the parent class constructor executes, then the derived class constructor
executes. They execute in the order of their derivation. Base class constructors
always execute first followed by any derived class constructors, in derivation
order. The constructor for the most derived class always executes last.
The same reasoning is true for destructors, except reverse the order of their
execution. When you destroy the most derived class, its destructor automatically
executes first, and on up the chain of derivation until the base class destructor
executes last. In other words, LIFO: Last In First Out.
The way constructors and destructors work by default in the Clarion language is
the same way they work in most other OOP languages. However, Clarion does
give you some flexibility that some other OOP languages do not.
Automatic overriding of Constructors and Destructors does not happen in
Clarion. The key word here is “automatic.” You can override them in Clarion if
you want to –something you cannot do in some other OOP languages. If you add
the REPLACE attribute to the prototype of your Construct or Destruct method,
you are telling the compiler that you do want to override the method.
So, what does that buy you? Suppose you need to initialize a variable in your
derived class before the parent class constructor executes. The only way to do that
is to override the constructor. You simply add the REPLACE attribute on the
prototype of the derived class Construct method, then write your code.
The concern about automatically overriding constructors and destructors is still
valid, and you should consider it carefully when you manually override them.
Nevertheless, the designers have thought of that. First, another small detour and
then I will return.
PARENT
You recall that the way to reference the current object within the code of a method
is to use SELF instead of the object name. There is another tool in Clarion’s OOP
syntax that allows you to call a method from a parent class even when you have
overridden that method.
30
The Theory of OOP
Prepending PARENT to the method name explicitly calls the parent class’s method,
even when overridden. This technique holds true not only for constructors and
destructors, but also for any overridden methods.
ClassA.Construct PROCEDURE
CODE
SELF.Aproperty = GLO:Flag !Initialize then call
PARENT.Construct ! parent constructor
In the above listing, the assumption here is that MyClass writes to GLO:Flag. But
when that happens, the derived ClassA class needs to grab the value of GLO:
Flag before that happens. In addition, your design says you cannot change the
code in MyClass. Without the REPLACE attribute, you have painted yourself in a
corner.
When you need explicit control of the execution order of your constructors or
destructors, you simply put the REPLACE attribute on the prototype. Then, in your
constructor or destructor method’s code, directly call Parent.Construct or
Parent.Destruct at the exact point within your logic that is most appropriate to
what you need to do. That could be before, after, or in the middle of your derived
class’s code – wherever you need it to be.
31
Programmi ng Objects In Clari on
Polymorphism
Polymorphism is the last major OOP buzzword you must learn. For those who are
wondering, it does not mean you have a parrot with a drug problem.
Polymorphism - derives from two Greek words. It is the prefix poly- meaning
“many” and the suffix -morphos meaning “form”. Combine them together and you
have a word meaning “many forms”. Add the suffix, “-ism”, and you have a new
word. For you grammar types, it is also a noun, denoting a state, condition, or
characteristic.
Polymorphism is a key point of object orient programming. However, it is not
exclusive to OOP. Clarion supports various forms of polymorphism. For example,
you may recall the *? and ? parameter types. These mean an unknown data type
for parameter passing. Even Clarion for DOS supported these.
When Clarion for Windows first appeared, another form of polymorphism
arrived. It is function overloading. Function overloading happens when you have
two procedures with the same label but different prototypes. Examples of this
form of polymorphism are numerous in the Clarion language, such as the OPEN
and CLOSE.statements. You can OPEN or CLOSE windows, files, reports, etc. and
those statements know how to respond to each type of parameter, because there
are actually multiple functions declared with the same names, one for each
parameter type.
Virtual methods
Virtual methods are an important part of polymorphism. What does “virtual”
mean?
Virtual- adj. 1) Being such in power, force, or effect, though not actually or
expressly such. They are reduced to virtual independence on charity. 2) Apparent, but
not actually in existence. (From optics). You can see your virtual image in a mirror.
The virtual method concept drives procedural programmers crazy. When you say
“virtual method” you are using the word “virtual” as an adjective. You are
describing a method that seems to be in force, or is apparent. Let me lift the fog on
this a little bit by giving you another definition:
Virtual method - A method whose prototype is present in a parent class and a
derived class, which both have the VIRTUAL attribute.
32
The Theory of OOP
That is all you need to do to create a virtual method. You may be thinking that I
did not clear any fog from this concept. Nevertheless, stay with me here as the
above definition is very important. Again, this requirement means that any virtual
method declared in the parent class and the derived class and both must have the
VIRTUAL attribute. These methods must have the same label and prototype. So this
not the same thing as function overloading, where the label is the same and the
prototype is differnet.
All right, what does this give you? What is so special about virtual methods? Let
me put it in context for you. Recall that a derived class has methods that can “call
up” to the parent class. You do this by using PARENT as the object name in the
method code. This aligns well with the procedural concept. You also know that
you declare any derived class after its parent. If you do not, you get compiler
errors, as compilers do not take kindly to forward references.
However, a virtual method allows a parent class to call the overridden child
method. You could think of it as a forward reference that actually works.
So what good is that? What benefit could you possibly get from something like
this? For one, this means that you can change a parent class behavior without
touching the code in it. It also means the parent knows an overridden method is
in a child class and will use that method instead of its own!
The best way to illustrate this is with some simple code. I am returning to the
apple pie example, but with some changes.
ApplePie CLASS,TYPE
PreparePie PROCEDURE
CreateCrust PROCEDURE,VIRTUAL !Virtual Methods
MakeFilling PROCEDURE,VIRTUAL
END
Dutch CLASS(ApplePie)
CreateCrust PROCEDURE,VIRTUAL !Virtual Methods
MakeFilling PROCEDURE,VIRTUAL
END
33
Programmi ng Objects In Clari on
CODE
Dutch.PreparePie !Call the Dutch object’s Virtuals
ApplePie.PreparePie PROCEDURE
CODE
SELF.CreateCrust
SELF.MakeFilling
First, you may make some assumptions about this code. Even a non-cook knows
there are some steps involved in preparing any pie. This code is in the
ApplePie.PreparePie method, as the assumption is that the series of steps
(create crust, make filling) will not change.
The Dutch class derives from its parent, ApplePie. You can see this in the class
statement for it. It also has CreateCrust and MakeFilling methods like its
parent. Notice that in both declarations, the VIRTUAL attribute is present.
In the main body of code, there is one line, Dutch.PreparePie. You know this is
legal as Dutch inherits the PreparePie method from its parent. Thus, there is only
one version of the code for PreparePie and this method belongs to ApplePie. In
addition, due to inheritance you know you do not have to code a
Dutch.PreparePie method.
OK, that is all fine for the easy stuff, like derivation and inheritance and you know
why the Dutch.PreparePie call is legal.
So where is the code for PreparePie? It is in the ApplePie class – only. You can
see the code. It creates a crust and makes the filling.
When the call to Dutch.PreparePie executes, it is really using the code from
ApplePie.PreparePie. Thus, the two lines of code execute as if this is the code:
ApplePie.PreparePie PROCEDURE
CODE
Dutch.CreateCrust
Dutch.MakeFilling
You should now see that the parent is calling down or forward to its child class
and executing its method as if it were its own. You could also surmise that if there
were a Grandmas and American apple pie classes derived from ApplePie, they too
would use the inherited PreparePie method (assuming the same prototype and
virtual attributes).
This also means that you do not have to change the parent class because the
virtual override means the parent executes the child method instead – all without
extra coding.
34
The Theory of OOP
DERIVED
The DERIVED attribute also available for virtual methods. In fact DERIVED means
“virtual”. To be more precise, it means “virtual protection”. This attribute is for
child methods only. All the rules of virtual methods apply when you use DERIVED.
What the DERIVED attribute offers is prototype protection. It enforces the
definition of virtual methods, especially the prototype of overridden methods.
Let us say you have a parent virtual method that takes a SHORT as a parameter. As
your project matures, many derived methods must use a SHORT as a parameter.
However, you discover the parameter should be a LONG instead, and so you make
the change in the parent class.
While the change may be technically correct, what you really accomplished was
converting a virtual override to a function overload. It is possible to get a clean
compile, depending on the child method’s code. However, the derived methods
no longer have a matching method in the top-level class, and so they don’t get
called instead of the parent method which now has the changed prototype. When
you test the change, you find the application does not function as it did before, or
perhaps worse.
Of course, you discover the real problem and change the derived methods so the
virtual methods are back in place. But did you get them all? How can you be sure?
The rule of thumb is that virtual methods in a base class have the VIRTUAL
attribute on all virtual methods. Any classes derived from it with virtual methods
use the DERIVED attribute. Thus, when you do make a prototype change, the
compiler knows you want virtual overrides, not function overloading. It will
signal this by giving you a compiler error when it no longer finds a parent method
for a DERIVED method. Use the error editor to fix the prototypes in all child classes
used. Then re-compile your project.
35
Programmi ng Objects In Clari on
Late Binding
Early or static binding is the stuff taught in your programming theory class, where
all procedure calls are resolved at compile time. However, since calls to virtual
methods cannot be resolved at compile time, the compiler must build a Virtual
Method Table or VMT at compile time and set up the method call for late or
dynamic binding. At run time, when the call to the virtual method executes, late
binding means that the actual method to call is determined by a lookup into the
VMT. You already know a lot about lookups, since it is a standard database
application technique.
Now, in many other OOP languages this late binding for virtual methods can
cause a real performance hit. However, in Clarion the entire late binding process
takes only one extra reference at runtime than early binding, so there’s no
performance penalty in Clarion.
Actually, virtual methods in Clarion are so efficient that when you look at the code
generated by the ABC Templates, you will find that almost all the code generated
is now in virtual methods.
That brings me to the next issue, which is the special scoping rules that apply to
classes declared inside Clarion procedures.
In an ABC application, most of the work in any given procedure is usually done
by an instance of one or more ABC classes. Embedded code is actually generated
as virtual methods belonging to derived classes, and these methods then add new
behavior to those classes. When ABC first appeared on the scene in Clarion 4 beta
releases, testers quickly discovered a major problem: they could not use
procedure local variables in embed points.
Local Derived Methods are in a derived class structure within a PROCEDURE. The
Local Derived Method definitions (meaning the executable code) must
immediately follow the end of the procedure in which they are declared. I
mentioned this earlier.
The ABC designers suggested using references to local variables, but this was a
cumbersome approach. The logical solution was to allow classes declared inside
36
The Theory of OOP
procedures, to see all of the procedure’s data. And that is how it now works. Local
derived methods inherit the scope of the procedure within which they are
declared. That means that all the procedure’s local data variables and routines are
visible and available inside the local derived methods. It also enables you to call
the procedure’s routines from within the method, just as if your code were still
within the procedure itself.
This new implementation of scoping is what allowed the designers to take the
OOP concept to the hilt in the ABC Templates and ABC Library. All you need to
do is run the App Wizard on any dictionary, then look at the generated code to see
that most generated procedures now contain very little code – everything is in
local derived methods. And you are still be able to write your embed code as if it
were inside the procedure itself. So in this aspect, you are not changing the way
you use embeds.
Interfaces
37
Programmi ng Objects In Clari on
Printer CLASS,TYPE
DocName CSTRING(256)
PrintDoc PROCEDURE
END
Printers today are more versatile than what the above code represents. They could
print in color, number of copies, duplex, etc. If you want, you could imagine
many more properties and methods, but I wish to keep this simple.
The Printer class prints a document. Say your marketing department has the
results of a survey from your customers, and they want more features. So the
design department manufactured a new printer with many new abilities. It is a
multifunction printer as it can operate as a fax too. Not only that, but also this
new device has scanner and copier behaviors.
Here is your dilemma: Do you recode the stable and reliable Printer class, thus
risking introducing bugs? Or do you keep the working code? Of course, some
good copy and paste should preserve the working code if you re-code it. However,
what happens about future enhancements? What do you do when the design
department comes up with yet another new feature? Do you really want to go
through this again?
I prepend my interface lables with the capital letter “I” so I can tell them from
classes. That is not a requirement of Clarion. I use this naming convention in my
code.
Before I get carried away, a few notes about coding interfaces:
• No properties are allowed, as the INTERFACE defines a behavior.
• All methods declared in an INTERFACE are implicitly VIRTUAL. You
may add the VIRTUAL attribute if you wish.
• All INTERFACEs are implicitly TYPEd, however you may add the
TYPE attribute if you wish.
38
The Theory of OOP
You declare an INTERFACE before any CLASS that IMPLEMENTS them. You may even
wish to INCLUDE them in your source. If so, the recommendation is to use INT as
the file extension, although this is not required, only customary.
Now that the IFax INTERFACE is ready, the Printer class needs to know about it.
The code now looks like this:
IFax INTERFACE
TransmitDoc PROCEDURE
GetDoc PROCEDURE
END
Printer CLASS,IMPLEMENTS(IFax),TYPE
DocName CSTRING(256)
PrintDoc PROCEDURE
END
What does this do anyway? It doesn’t add the code for any methods to the class,
but it adds the definitions for those methods to the class. You are adding new
behaviors while not affecting the existing code. To put this in other words, the
printer knows how to be a fax, but it did not forget how to be a printer. You could
also do something like this if you really do not wish to change the Printer class:
39
Programmi ng Objects In Clari on
IFax INTERFACE
TransmitDoc PROCEDURE
GetDoc PROCEDURE
END
Printer CLASS,TYPE
DocName CSTRING(256)
PrintDoc PROCEDURE
END
ExtendPrinter CLASS(Printer),IMPLEMENTS(IFax),TYPE
PrintDoc PROCEDURE
END
When you IMPLEMENT an INTERFACE, you do need to write the code for the
INTERFACE methods. If you do not, the compiler will remind you. You write the
code in the same area as you would any other method, using the same mechanics.
However, the compiler needs to know which method belongs to a class and which
method belongs to the INTERFACE.
Using the above code, you could see a method for an INTERFACE written
something like this:
ExtendPrinter.IFax.TransmitDoc PROCEDURE
CODE
!Code here.
Notice that the class label comes first, then the INTERFACE label followed by the
method. Each separated by a period (see dot syntax, discussed earlier).
So what’s the benefit of this, if you still have to write the method code? Simply that
if a class implements a specific interface, you know that there are certain methods
that it will have. And that lets you treat the class as if it were an instance of the
interface.
40
The Theory of OOP
happens if an interface has many methods? How do you show which ones you
should implement?
The simple answer is you implement all of them. David Bayliss, the designer of the
ABC classes and Clarion compiler architect, explains that the problem is that an
interface should be an explicit (not implicit) contract between the interface and
the class that implements it. In other words, you know what the class should do.
In addition, if another programmer implements your interface in his class, then he
knows what to expect.
An interface is a common boundary between two objects. If one can choose
behaviors, then the interface is implicit, or ambiguous. In Clarion the interface is
explicit, and unambiguous.
You may be thinking this is a little like multiple inheritance, and in fact interfaces
are how single-inheritance languages like Clarion get multiple-inheritance
benefits.
If you expect to implement many interfaces in a class, then think about using
derivation in your interface declarations.
IFax INTERFACE(ICopier)
TransmitDoc PROCEDURE
GetDoc PROCEDURE
END
I’ve defined and show the rules of what an interface is and does and how to use
them. In the next chapter I will demonstrate interfaces in action.
41
Programmi ng Objects In Clari on
Summary
You now know the three major OOP buzzwords: Encapsulation, Inheritance, and
Polymorphism, and they are implemented in the Clarion language. You have also
heard the other standard OOP terms: Properties, Methods, Objects, Instantiation,
Base Classes, Derived Classes, Interfaces, SELF, PARENT, Constructors and
Destructors, Virtual Methods, and Late Binding.
The information in this chapter lays the foundation for what comes in the next
chapters. You will discover that this information applies to ABC, and to what
happens when you use embed points.
42
Chapter 3
APPLYING OOP IN CLARION
The last chapter covered the basics of Object Oriented Programming. Its purpose
was to get the theory of OOP under your belt. This chapter covers how to apply
that data. I plan to do this with actual working code that covers many of the
concepts covered. Since there are many possible ways to do this, you may see a
concept applied twice, but differently.
This means repeated concepts, but the difference is that working code reinforces
these concepts.
Where to Start?
One of the problems with using objects in your applications is trying to find a
good starting point. Before you write one line of code, think about what you need
and want.
Think about the FileClass discussed previously, which deals with things that are
“file- like.” Again, what do all files have in common? This common functionality is
what you usually put in a base class. Always start so simple is it painfully obvious
when you are missing functionality. Do not worry about missing things at first. In
the case of the FileClass, if you can design and code a class that opens and closes a
file, you are off to a good start. If you can make this class open and close any file,
no matter what, then you are further along and “bang on” the right track.
43
Programmi ng Objects In Clari on
The point is that an object does not know, nor care about, the specifics of what it
is dealing with. At least it does not during development time. This means that you
avoid code that applies only to a specific situation, or a specific file, and so on. If
you need a few hard coded values bit here and there to get you going, that is fine.
Just see if you can retain the function without hard coded values later.
The question I think gets asked the most is when one should convert existing
applications to OOP. I have seen all types of applications in various stages of
development. There is no precise answer as there are too many variables. Where
applications are close to release, I can state this is the wrong time to think about
converting existing code to objects. In this case, hold off until the next major
version of your application.
It is never too late to use objects for code not yet written. This means that
applications that started with the Clarion templates and then are maintained by
hand (a common practice), can benefit from objects. Even the Clarion 6 legacy
templates now get most of their new functionality via ABC objects that have been
grafted into the old legacy way of doing things.
The next type of application is one that has some code written, but release is still
way off in the future. This could benefit from using objects as above, but in
addition to that, there is a greater chance to convert existing code to objects.
Whether or not this happens depends on the design, the skill set of the
developers, and if there time in the budget to squeeze a few of these changes in.
An interesting phenomenon happens here. It may take some time to do a
conversion and thus it will not seem worth the effort as it puts the project behind.
What is missing from that viewpoint is that when you have an object you can use,
that is a bit of code you do not need to write again, so there is a timesaving here.
The payoff comes when you start using these objects. Therefore, you could say
that coding OOP style is front heavy.
The last type of application is the new one. Decisions are easy at this stage as the
real code work has yet to start. One could start a new ABC project and many
Clarion developers choose this route with success.
Those are three broad scenarios to look at. But you are the one who will have to
decide when and how you wish to proceed.
44
Applyi ng OOP in Clarion
Back to Encapsulation
Unlike the previous chapter where I described the theory of encapsulation, this
section shows some actual code use. Again, starting very simply, here is the CLASS
(which is encapsulation):
PlayWave CLASS,TYPE
WaveName CSTRING(FILE:MaxFileName)
Play PROCEDURE
END
That is a simple class with one property and one method encapsulated in it.
Notice the WaveName property, which contains the name of the wave file.
FILE:MaxFileName is an EQUATE which happens to be 256. Therefore, this
declares a CSTRING variable, 256 bytes long (minus one for the terminating
character). Since it is a CSTRING, you do not need to CLIP it.
However, notice that this class makes no mention of which wave file to play. There
is no mention of the file name in the method either.
You may guess, based on the name of the method, it plays the wave file in
WaveName. This is a correct assumption. However, there is something unusual
about the Play method. Have you spotted what it is yet?
WaveName does not take any parameters. How does the Play method know which
wave file to play?
Remember, we are talking about encapsulation here. Inspect the following method
code listing and see if you can see how this works.
PlayWave.Play PROCEDURE
CODE
SndPlaySound(SELF.WaveName,1) ! Call API to play .WAV file.
RETURN
Function Overloading
Since this is code inside of a method, the object name is SELF. Thus, any instance
of PlayWave works. PlayWave is not instantiated due to the TYPE attribute. This
means that this class is a definition only. In order for this to work, the PlayWave
class must be instantiated before you attempt to use it in code.
45
Programmi ng Objects In Clari on
Before I use this class, this is a good time to bring up that a class definition does
support function overloading. The declaration of the PlayWave class can become
something like this:
PlayWave CLASS,TYPE
WaveName CSTRING(FILE:MaxFileName)
Play PROCEDURE
Play PROCEDURE(STRING xWaveName)
END
Notice the two Play methods. One takes a parameter the other does not. Yet, both
have the same label. This is function overloading and this is legal in Clarion. The
rule is that any procedure you can declare in a MAP statement, you may declare in
a class.
The TYPE attribute tells you this is only a definition. Before you may use an object,
it must be there first. Thus, you need to instantiate the object the above definition
describes. How do you instantiate this class?
One line of code is all you need, which can appear anywhere after the class
declaration and before the CODE statement.
MakeNoise PlayWave !Instantiate the PlayWave class
When this program starts, the MakeNoise object is instantiated. Assuming there is
a window declaration with two button controls on it, you could call these
methods when these buttons are pressed. The code could look like this:
ACCEPT
CASE ACCEPTED()
OF ?PlayButton
MakeNoise.WaveName = LONGPATH() & ‘\halle.wav’
MakeNoise.Play()
OF ?PlayTooButton
MakeNoise.Play(LONGPATH() & ‘\halle.wav’)
END
END
Notice that the code is calling both methods. The first sets the name of the wave
file to play, then calls the method. This works because the code for this method
uses a property of the class. The second uses the name of the file as a parameter.
This works as well.
This is a clever thing to do with a class. If you function overload two or more
methods, this makes your class flexible. This does not always mean that two or
46
Applyi ng OOP in Clarion
more methods do the same task; the overloaded methods may do something
different. Which approach you use is up to you.
Here’s the method code:
PlayWave.Play PROCEDURE
CODE
SndPlaySound(SELF.WaveName,1)
RETURN
Notice that each method has the prototype listed. This is so the compiler knows
which method belongs to which declaration. You do not have to use the attributes
or return types here (if any). I should mention that some OOP coders do use
attributes and return types (if any), but place them as comments at the end of the
line.
Also, notice that the name of the declaring class is used. Do not use the name of
instantiated objects! See OOP1.PRJ in the code folder for a working example.
Using this method (no pun intended), you may declare your class structure any
way you want, then simply drop in the minimum method code. Then you can test
a few methods at a time, so even large class structures are not a concern. You
could even call these “empty” methods in your testing. Nothing happens when
you do.
47
Programmi ng Objects In Clari on
You now have a working class labeled PlayWave. As this code progresses, it is
getting unwieldy with many declarations. Thus, the PlayWave class changes as
follows:
PlayWave CLASS,TYPE, | MODULE(‘PLAYWAVE.CLW’),|
LINK(‘PLAYWAVE.CLW’)
WaveName CSTRING(FILE:MaxFileName)
Play PROCEDURE
Play PROCEDURE(STRING xWaveName)
END
48
Applyi ng OOP in Clarion
then do not include it again. The ONCE attribute makes the following type of
“dirty” declaration obsolete:
INCLUDE(‘SomeInclude.inc’,_SomeInclude_)
_SomeInclude_ EQUATE(1)
The code after the MEMBER statement is the same method code we have seen
already. The only difference is this code is no longer in the main source module.
Thus, you may keep your main program logic in one file. Simply INCLUDE
whatever you need, to keep this module free of “clutter”.
If you need many instances of a class, this is the easiest way to do it. In addition,
each instance of a PlayWave class inherits everything from PlayWave. Assuming a
window with buttons, the following code in the main body works:
ACCEPT
CASE ACCEPTED()
OF ?OkButton
Noise1.WaveName = LONGPATH() & ‘\f22.wav’
Noise1.Play()
OF ?OkButton2
Noise2.WaveName = LONGPATH() & ‘\train.wav’
Noise2.Play()
OF ?OkButton3
Noise3.WaveName = LONGPATH() & ‘\a10flyby.wav’
Noise3.Play()
END
END
Manual instantiation
So far, all the examples use simple instantiation. Manual instantiation is just as
effective, but you need to keep track of what you are doing. You may need manual
instantiation if you have references in a class structure.
49
Programmi ng Objects In Clari on
Assuming the PlayWave class is the same, what follows are a few more
declarations:
TypeQue QUEUE,TYPE
MyString STRING(15)
END
WorkC1ass CLASS,TYPE,MODULE(‘WORKCLAS.CLW’),|
LINK(‘WORKCLAS.CLW’)
WorkQ &TypeQue
Noise &P1ayWave
Fi11Que PROCEDURE,LONG,PROC
END
The TypeQue structure is simply a QUEUE definition. The TYPE attribute means the
same here as it does on a CLASS structure.
The WorkClass declaration is a definition as well. Again, the MODULE and LINK
attributes are present. However, notice that there are two properties in WorkClass.
They are simply references to other declarations. Therefore, you can see that class
structures can accommodate complex structures. By using a reference, it is the
same as if the declarations are in the class structure. You are not limited to QUEUE
and CLASS structures. You may use a reference in any manner a reference is legal
elsewhere in Clarion. This means you may have &WINDOW, &FILE and other
complex structures referenced.
The method in WorkClass returns a value. The PROC attribute prevents a compiler
warning, as PROCEDUREs did not return values in earlier versions of Clarion. This
was reserved for FUNCTIONs. FUNCTION is a deprecated language statement.
You can create an instance of WorkClass, just like PlayWave:
Work WorkClass !Create an instance of the WorkClass object.
OK, there is nothing new here. Where do the references come in? If you tried to
use them in code at this point, you will get a GPF. The reason is that there is no
memory allocated for them – yet.
Which means you must instantiate them. The NEW statement does this as follows:
50
Applyi ng OOP in Clarion
ACCEPT
CASE EVENT()
OF EVENT:OpenWindow
Work.WorkQ &= NEW TypeQue !Create instance of TypeQue
? ASSERT(~Work.WorkQ &= NULL)
Work.Noise &= NEW PlayWave !Create instance of PlayWave
? ASSERT(~Work.Noise &= NULL)
IF Work.FillQue()
?Listl{PROP:From} = Work.WorkQ
SELECT(?Listl,l)
END
The way to do this is to make a reference assignment with the NEW statement. The
ASSERT is assuming that the reference is not NULL. If this is not the case, you can
GPF the program, but only if in debug mode. You can spot this by the question
mark in column one (which is why they are red in color in the editor). When in
debug mode; GPFing a program is a fast and easy way to find bugs in your code.
Placing ASSERT statements in your code is a good way to “bullet proof” your code.
Since the FillQue method returns the number of items in a QUEUE structure,
using it with the IF statement acts like a Boolean function. If there were no entries
in the QUEUE, the method returns zero, thus the IF statement fails. In Clarion, a
zero is false, anything else is true. If Work.FillQueue returns true, then the ?List
control gets its data from the Work.WorkQ. The PROP:From expression is what
makes this work. Finally, the SELECT statement selects the first record in the list
box.
Notice the test of “nullness”. If the queue reference is already NULL, the code drops
through to the next statement. If not, then FREE deletes all entries from a QUEUE
51
Programmi ng Objects In Clari on
and de-allocates the memory they occupied. It also de-allocates the memory used
by the QUEUE’s “overhead.” Then the code destroys the reference to Work.WorkQ by
using DISPOSE.
You can see a similar test for the Work.Noise object.
For further study, see OOP3.PRJ in the code folder.
If you ever need to use constructors and destructors in your classes, a good rule of
thumb is to place code that always needs to execute in them. An example is
instantiating another object in the case of a constructor. If I use a constructor, I use
a destructor to clean up what the constructor did.
Here is a good example of this. I have this class definition:
ConstructClass CLASS,TYPE, |
MODULE(‘CNSTRUCT.CLW’),|
LINK(‘CNSTRUCT.CLW’)
WorkQ &TypeQue !Reference to typed queue
Noise &PlayWave !Reference to typed class
FillQue PROCEDURE,LONG,PROC
Construct PROCEDURE
Destruct PROCEDURE
END
ConstructClass has two properties that are references, one to a queue and the
other to another class. Since I need to make reference assignments to both before I
can legally use them, the code to do this goes in my Construct method. The
Destruct method merely undoes what Construct did.
52
Applyi ng OOP in Clarion
ConstructClass.Construct PROCEDURE
CODE
SELF.WorkQ &= NEW(TypeQue) !Instantiate the queue
? ASSERT(~SELF.WorkQ &= NULL) !Assert that it is not null
SELF.Noise &= NEW(PlayWave) !Instantiate Playwave object
? ASSERT(~SELF.Noise &= NULL) !Assert that it is not null
ConstructClass.Destruct PROCEDURE
CODE
IF ~SELF.WorkQ &= NULL !If the queue is not null
FREE(SELF.WorkQ) !Free the queue
DISPOSE(SELF.WorkQ) !destroy the queue
END
IF ~SELF.Noise &= NULL !If Noise object is not null
DISPOSE(SELF.Noise) !destroy the Noise object
END
Cnstruct.clw is where you find the above code. The LINK attribute on the class tells
the linker to add this to the project, even though you do not see it there.
53
Programmi ng Objects In Clari on
Derivation
The previous example has a reference to another class in its own class definition.
This does give you derivation when the object is instantiated (in the Construct
method). However, this is not the only way to do this:
DerivedClass CLASS(PlayWave),TYPE
WorkQ &TypeQue
FillQue PROCEDURE,LONG
Construct PROCEDURE
Destruct PROCEDURE
END
As you can see in this example, the reference to the parent class is missing.
However, the parameter in the class statement does the same thing.
This makes the workload on the constructor and destructor less as well. This is
not much different from the previous example; more of a variation to illustrate
there is more than one way to get the benefit of parent classes.
See OOP5.PRJ in the code folder for an example of this.
Overriding
Sometimes, you need to override a parent behavior. However, do not confuse that
to mean that overriding is an all-or-nothing situation. Sometimes it is, if that is
what you want. However, what if you wish to extend the parent’s behavior? You
do have to override the method. You do this by making a method with the same
label and prototype as the parent method.
However, in the code of the method there is no rule that says you cannot use the
parent method. In ABC, this is a common practice. Here is the class to illustrate
this:
54
Applyi ng OOP in Clarion
OverrideClass CLASS(PlayWave),TYPE,|
MODULE(‘OVERRIDE.CLW’),|
LINK(‘OVERRIDE.CLW’)
WinRef &WINDOW !Reference to a window
Control LONG(0)
FlashSpeed LONG(0)
Toggle LONG, STATIC !Ensure value does not change
Flash PROCEDURE
Play PROCEDURE !Override method in Parent
Init PROCEDURE
END
The Play method in OverrideClass overrides the Play method in the PlayWave
class. You may assume that the parent Play method plays a wave file, like the
earlier examples. The OverrideClass.Play method needs to do more than
simply play a wave file. There is code that displays messages and does a few other
things. In the source file for this method (see the MODULE attribute for the name of
the file), there is this code:
OverrideClass.Play PROCEDURE ()
CODE
SYSTEM{PROP:Font,1} = 'Verdana'
SYSTEM{PROP:Font,2} = 8
SYSTEM{PROP:Font,4} = FONT:Bold
MESSAGE('Before parent call: |I am the "PLAY" method ' |
& 'in override class!','Hello...',ICON:Exclamation)
PARENT.Play() !Call Play method in PlayWave (PARENT) class
SYSTEM{PROP:Font,4} = FONT:Italic
SYSTEM{PROP:Font,2} = 10
MESSAGE('After parent call: |I am the "PLAY" method ' |
& 'in override class!','Hello...',ICON:Exclamation)
RETURN
Do you see the call to the parent’s method? PARENT is like SELF, but defined as
whatever the parent is. In this case, it is PlayWave. Therefore, the OverrideClass
never plays a wave file, as there is no code to do so. Instead, it uses another
method that does have that code.
55
Programmi ng Objects In Clari on
In this instance, the method needs to not only play a wave file, but also display
messages. In addition, there is some code to change the font of the message boxes.
When run, it appears as shown in Figure 3-2.
Virtual methods
Perhaps the hardest thing for those studying OOP is to understand how virtual
methods work in their code. It is a strange concept for procedural, top-down
coders to grasp. I have had students tell me they cannot understand how virtual
methods work because the code jumps all over the place. And they are right. Of
course, confusing code is nothing new. When the ACCEPT loop first appeared, that
was perplexing to many developers too. In the old legacy (pre-C6 Clarion
templates), routines with nearly identical names call each other and back.
ABC uses many virtual methods. There is more code “action” going on here, so
perhaps the confusion stems from the increased activity. It really does not matter,
as once you understand how virtual methods work and why you use them it’s a
walk in the park.
Here are two examples. The first carries on the current theme from the example
projects:
56
Applyi ng OOP in Clarion
SECTION('PLAYWAVE')
PlayWave CLASS,TYPE,MODULE('PLAYWAV2.CLW'),|
LINK('PLAYWAV2.CLW')
WaveName CSTRING(FILE:MaxFileName)
Play PROCEDURE (),VIRTUAL
CallVirtual PROCEDURE ()
END
SECTION('VIRTUAL1')
Virtual1 CLASS(PlayWave),TYPE,MODULE('VIRTUAL1.CLW'),|
LINK('VIRTUAL1.CLW')
Play PROCEDURE (),DERIVED
END
SECTION('VIRTUAL2')
Virtual2 CLASS(PlayWave),TYPE,MODULE('VIRTUAL2.CLW'),|
LINK('VIRTUAL2.CLW')
Play PROCEDURE (),DERIVED
END
SECTION('RESTOFPROGRAM')
There is now a change to the PlayWave class. Notice the additional CallVirtual
method. Also, notice the source where you find this method. The two almost
identically named classes derive from PlayWave.
Notice the Play method in each class. The two child methods override the parent.
The parent method has the VIRTUAL attribute, which is required. The two child
methods use the DERIVED attribute. Remember that DERIVED means “virtual” plus
it protects the child methods from function overloading if the parent prototype
changes.
Lastly, notice the SECTION statements. This is not required for coding objects, but
is rather a clean way of including the definitions in the method source file. Take a
look at the source files.
57
Programmi ng Objects In Clari on
INCLUDE('OOP7.CLW','PLAYWAVE'),ONCE
PlayWave.Play PROCEDURE ()
CODE
MESSAGE('I am Play!','Base Class',ICON:Exclamation)
SndPlaySound(SELF.WaveName,1)
RETURN
PlayWave.CallVirtual PROCEDURE
! Call whatever virtual is currently
! replacing the dummy PARENT.Play
! method shown above.
CODE
MESSAGE('I am CallVirtual!','Base Class',ICON:Exclamation)
! Self.Play calls either V1.Play or V2.Play
! depending on which one called this method.
MESSAGE('Back at CallVirtual')
Notice the INCLUDE statement at the top of the listing. It includes the code starting
after the “Playwave” SECTION name. It includes everything until the next SECTION
or end of file, whichever comes first.
There are two methods, Play and CallVirtual. CallVirtual, when run, calls
Play sandwiched by two message statements. They are there only to show what is
going on when the code runs, and to demonstrate more code than simply playing
wave files.
Next, the virtual1.clw file (Virtual2.clw is nearly identical, so no need to look at it).
INCLUDE('OOP7.CLW','VIRTUAL1'),ONCE
Virtual1.Play PROCEDURE ()
CODE
PARENT.Play ()
MESSAGE('Hi, I''m Virtual 1 ','Virtual 1...',ICON:Exclamation)
RETURN
Notice the two INCLUDE statements. Both are needed as the first has the parent
definition and the second INCLUDE is the definition of class derived from it.
All this method does is call the parent method and show a message. The parent
call does run the code to play a wave file. If you comment that line, the virtual
methods still execute (just no sound).
58
Applyi ng OOP in Clarion
Press Go and the main window of the application appears. The code to run
executes when you press ?OKButtonv1. The debugger takes over and you press
Step Source to follow the execution. Since this project is small, you can see what
happens in the code. You may need a few loops through that code to get a better
understanding of this. It is worth the small amount of time it takes.
59
Programmi ng Objects In Clari on
MAP
END
ApplePie CLASS
PreparePie PROCEDURE
CreateCrust PROCEDURE,VIRTUAL !Virtual Methods
MakeFilling PROCEDURE,VIRTUAL
END
Dutch CLASS(ApplePie)
CreateCrust PROCEDURE,DERIVED !Virtual Methods
MakeFilling PROCEDURE,DERIVED
END
ApplePie.CreateCrust PROCEDURE
CODE
MESSAGE('Creating the Apple Pie crust.')
60
Applyi ng OOP in Clarion
ApplePie.MakeFilling PROCEDURE
CODE
MESSAGE('Making the Apple Pie filling - yum!')
Dutch.CreateCrust PROCEDURE
CODE
MESSAGE('Creating the crust for Dutch Pie.')
Dutch.MakeFilling PROCEDURE
CODE
MESSAGE('Making the Dutch filling - yum!')
In the main code body, there is enough code to make two different apple pies. The
main thing to keep in mind is that the Dutch object does not have its own
PreparePie method. It inherited this from its parent. Thus, the PreparePie
method is the same as the CallVirtual method in the previous example
(functionally speaking).
When this project runs, PreparePie calls “down” or “forward” to the two Dutch
methods. The only action required by the user when running this, is to press the
OK button on each message box.
The use of virtual methods is fine and as you can see, you can get a lot done.
However, if the next developer that comes along wishes to use your class to make
a different type of apple pie, he must use your methods of making apple pies.
However, what if they want to use a new apple pie recipe with your class? Will it
work? Will they be forced to re-write your class?
The real question is, “Does the Pie Class know how to make various apple pies?”
61
Programmi ng Objects In Clari on
Where I am going with this is that a pie class should know how to make any pie,
apple or otherwise. This is where the interface enters the picture.
A class that implements an interface can now change its behavior without
changing the class code.
ITEMIZE,PRE(PIE)
Crust EQUATE !The same as PIE:Crust EQUATE(1).
Filling EQUATE !The same as PIE:Filling EQUATE(2).
END
IngredientQ QUEUE,TYPE
Ingredient CSTRING(101)
END
The first part of this code is simply another way of defining equates. The
IPieType is the interface. The methods are implied virtual methods. These
methods describe how one could make any type of pie. It would make sense to get
what type of pie one wishes to make. What are the ingredients for a pie is a good
thing to know. GetMethod describes how to make the pie. Lastly, pies do no one
any good if one cannot serve the pie when ready to eat.
Next is the type definition of the IngredientQ structure. This holds the list of
ingredients.
The PieClass is the base class. There are two references to the IngredientQ as
the ingredients for a filling are different from a crust. The Type is a reference
62
Applyi ng OOP in Clarion
PrettyPieClass CLASS(PieClass),TYPE
Make PROCEDURE
ShowReceipe PROCEDURE,DERIVED
END
PrettyPie &PrettyPieClass
Notice the use of the DERIVED attribute on the virtual methods. These two classes
also show two different ways of instantiation.
That is the code for the pie classes. Notice that the only thing these classes can do
is make pies. Yet, nothing here hints at what types of pies one can make. One
could make apple, chocolate, pumpkin or even mud pies.
This brings me to the rest of the declarations. See the following code.
63
Programmi ng Objects In Clari on
ApplePie CLASS,IMPLEMENTS(IPieType)
ListCounter LONG
IngredientQ &ApplePieIngredientQ
Construct PROCEDURE
Destruct PROCEDURE
END
AppleCheeseCake CLASS,IMPLEMENTS(IPieType)
ListCounter LONG
IngredientQ &ApplePieIngredientQ
Construct PROCEDURE
Destruct PROCEDURE
END
Notice there is another queue structure, as the design requires that crust and
filling ingredients use it. Thus, an ingredient type exists in the definition.
The two classes are identical except for label. Each class implements the IPieType
interface. The rest of the declaration is just like the previous classes: a reference to
a queue structure and the automatic methods for the household chores.
CODE
PieView.Init(ApplePie.IPieType) !Pass the Apple Pie interface
!into the PieView object.
PieView.Make()
PieView is the instantiated class. The passed parameter is the INTERFACE. That
does not look like much. Inspecting the Init method might reveal some
information.
64
Applyi ng OOP in Clarion
The reference assignment says which INTERFACE this method uses. This is passed
via the PT variable (which is an IPieType variable).
There are two loops here, one for the crust and the other for the filling. The key
line of code here is the interface method SELF.Type.GetIngredients(Type).
This is where the “magic” happens. Here is the code for this method:
65
Programmi ng Objects In Clari on
The queue structure looped in the code was preloaded in the constructors. It
simply returns the correct ingredient based on the ingredient type. In this
example, it is either a crust or filling type.
You can now see how the Init method fills its own IngredientQ by calling this
interface method. Once the Init method is finished, it’s time to make the pie.
This is the next line of code in the main body, the making of the pie.
PieClass.Make PROCEDURE !Call the VIRTUAL ShowReceipe method.
CODE
SELF.ShowReceipe()
That calls the object’s ShowRecipe method. This is virtual, thus there is nothing in
the PieClass.ShowRecipe method. Remember, a parent class calls the derived
method instead of its own when they are virtual methods.
Thus, PieView.ShowRecipe runs instead. It is the method where the user first
sees anything happening. It is the one that offers the choice of which window type
they want to use and then opens that window.
66
Applyi ng OOP in Clarion
CODE
EXECUTE(SELF.GetViewType()) !Method returns 1 or 2.
TheWindow &= ThreeDWindow !Reference assign if 1.
TheWindow &= FlatWindow !Reference assign if 2.
END
Method = SELF.Type.GetMethod() !Get the cooking method.
Suggestions = SELF.Type.GetSuggestions() !serving suggestions.
OPEN(TheWindow) !Open chosen window
!Set the window caption.
TheWindow{PROP:Text} = 'Pie Viewer Class: ' &
SELF.Type.GetType()
ACCEPT !And away we go.
CASE FIELD()
OF ?Close
CASE EVENT()
OF EVENT:Accepted
POST(EVENT:CloseWindow)
END
END
END
CLOSE(TheWindow)
The code first calls GetViewType to find out which type of window the user wants
to use. The EXECUTE statement then assigns the reference to one of two windows.
The GetMethod returns the instructions. Remember this is the interface method,
so this class “knows” how to get an outside behavior, which you control. The
GetSuggestions is the serving suggestion for an apple pie. If you made a
chocolate pie, you may want to code a different GetSuggestions method.
67
Programmi ng Objects In Clari on
68
Applyi ng OOP in Clarion
There is more to the example. Study PIE.PRJ in the code folder for more
information. Look at the PrettyPie objects. This uses the same interface;
however, the results are quite different, as shown in Figure 3-5.
69
Programmi ng Objects In Clari on
Summary
Interfaces are excellent ways to add new behaviors without touching existing
code. You do have to write the code for all the methods for a given interface. That
is the trade off. I feel that for the extra functionality you get with interfaces, the
small price of writing code for all the methods is worth it. Remember, you may
still code stub methods for behaviors you do not need.
Study and play with the provided code examples. Make changes to these projects.
Please break them too. Then fix them. The only real true way to “get” OOP is to
write code.
70
Chapter 4
ABC AND OOP
This chapter covers the ABC class library. ABC is an implementation of OOP, so
you can see the importance of understanding OOP first!
ABC made its debut with the release of Clarion version 4. ABC has evolved over
the years. Along the same time line, articles describing how to use various pieces
of ABC were penned. Bruce Johnson, of Capesoft (www.capesoft.com) fame, has
written a text on ABC. Clarion Magazine (www.clarionmag.com) is another
excellent resource. You will need a subscription to read most articles. In addition,
another excellent resource is James Cooke’s Clarion Foundry
www.clarionpublisher.com). I would be remiss without mentioning my own site,
RADFusion (www.radfusion.com).
This chapters covers certain areas to give you a better understanding of what is
going on under the hood of ABC, as well as ABC problems and solutions.
The average Clarion developer can use the template interface to the ABC objects
for common tasks like accessing data, developing browse lists and edit forms with
little or no difficulty. Just about every business application in the world requires
these tasks, and ABC handles them smoothly and without the developer having to
think about what’s going on in the code.
It is the uncommon areas, or exceptions to the rule, that sometimes give
developers fits. The other problem area is inexplicable behaviors. If you add to
this mix a lack of information (common when learning something for the first
time), it magnifies the difficulty of working with ABC.
71
Programmi ng Objects In Clari on
ABC does vast amounts of work for you. There is the old 90/10 rule – ABC does
90% of the work, you must do the last 10%. That last 10% can sometimes seem
like the entire workload.
Global objects
There are a number of derived objects that live in the global area of any
application. They come straight from ABC. You can find these objects in any
application by pressing Global, and scrolling to the right until you come to the
Classes tab. It will look similar to Figure 4.1 (you may not have data, embeds or
extensions, thus no green check):
Figure 4-1:
72
ABC and OOP
The names of the global objects as well as their settings are available here. For
example, pressing General displays the dialog in Figure 4.2:
Each class has a drop list. This is so that you may change to a different class
instead of ABC’s default. For example, here is what some of these setting would
look like if you changed some of the defaults to use the Clarion Handy Tools
(www.cwhandy.com) classes:
The other dialogs operate in a similar fashion. The moral of the story is that if you
wish to use your own classes for your global objects, ABC allows it.
73
Programmi ng Objects In Clari on
Global embeds
If you press the Embed button, you will see a tree list of points where you may
add your own code. The following figure shows the ABC global objects.
The global objects affect the entire application. Any embedded code placed here
will, of course, also affect the entire application. The reason is that your code
changes the way the methods of these global objects work, via virtual methods.
If you expand the File Managers embed, you will see a file manager object for each
file used in your application. While that could make for a busy embed tree for
74
ABC and OOP
large dictionaries, it does give you control over certain files, while leaving others
alone.
Look at the following figure.
Figure 4-5:
75
Programmi ng Objects In Clari on
You may gather that starting at 1 and then adding 1 to the last value is not the
behavior wanted by this application and on this file only. So what does this
embedded code do?
Look at the following figure:
Figure 4-6:
This is all the code needed to alter the default behavior. In short, instead of
starting at 1 and then adding 1 to the last saved value, it starts at 10 and
increments by 10. Thus, when adding details to an invoice, the line items are
numbered starting with 10, then 20 and so on.
A user could then edit line item numbers by, for instance, changing 30 to 15. This
would place the third line item between the first (10) and the second (20).
76
ABC and OOP
One final note about this naming convention; depending on the number of files in
the dictionary, the BC0 could become BC1, BC2 etc. Expect to see this for every ten
files.
The BC module contains code similar to this figure:
MEMBER('eip.clw')
MAP
MODULE('EIPBC0.CLW')
EIPBC0:DctInit PROCEDURE
EIPBC0:DctKill PROCEDURE
EIPBC0:FilesInit PROCEDURE
END
END
DctInit PROCEDURE
CODE
EIPBC0:DctInit
EIPBC0:FilesInit
DctKill PROCEDURE
CODE
EIPBC0:DctKill
This code simply declares a MAP structure and the procedures in it. The code for
the procedures must be there as well, and this is visible. The DctInit procedure
calls two procedures in this case.
In Clarion 6, this code is now found in its own class, specifically a constructor.
This is so that each thread has its own instance of the file and relation managers.
The DctKill method now lives in the destructor of the Dictionary class. When
the thread dies, so does that thread’s instance of the File and Relation manager
classes.
The procedure names come from the name of the application (EIP for “Edit In
Place”), the BC0 designation, a colon and the name of procedure. Let’s inspect the
code.
DctInit
First up is the DctInit procedure.
77
Programmi ng Objects In Clari on
EIPBC0:DctInit PROCEDURE
CODE
Relate:Item &= Hide:Relate:Item
Relate:Config &= Hide:Relate:Config
Relate:Customer &= Hide:Relate:Customer
Relate:InvDet &= Hide:Relate:InvDet
Relate:InvHdr &= Hide:Relate:InvHdr
This simply makes reference assignments to a class. That is all this does. The
Hide:Relate:<File> is derived from RelationManager, an ABC class. This
means that Relate:<File> could be thought of as grandchildren of
RelationManager.
Each of these “hidden” classes have methods in them, at the minimum an Init
and Kill method. There could be more methods depending on whether there are
embeds, since each embed means an automatic override of a virtual method. The
class definition for Hide:Relate:Item is as follows:
Hide:Relate:Item CLASS(RelationManager)
Init PROCEDURE
Kill PROCEDURE(),DERIVED
END
Hide:Relate:Item.Kill PROCEDURE
CODE
Hide:Access:Item.Kill
PARENT.Kill
Relate:Item &= NULL
DctKill
Next is the DctKill procedure:
78
ABC and OOP
EIPBC0:DctKill PROCEDURE
CODE
Hide:Relate:Item.Kill
Hide:Relate:Config.Kill
Hide:Relate:Customer.Kill
Hide:Relate:InvDet.Kill
Hide:Relate:InvHdr.Kill
This is simply a collection of procedure calls. For now, you may assume it undoes
DctInit.
FilesInit
Next is FilesInit.
EIPBC0:FilesInit PROCEDURE
CODE
Hide:Relate:Item.Init
Hide:Relate:Config.Init
Hide:Relate:Customer.Init
Hide:Relate:InvDet.Init
Hide:Relate:InvHdr.Init
A little bit of explanation is in order here. Each of these procedure calls is really a
method. These are really the parent methods, but recall the reference assignments
from earlier. If the preceding DctInit procedure did not happen (which did the
reference assignments), you would be getting GPFs at this point.
If you look back on the code for Hide:Relate:Item.Init, this is a method call in
another class, which is why SELF would be improper here.
Hide:Access:Item CLASS(FileManager)
Init PROCEDURE
Kill PROCEDURE(),DERIVED
END
You can see what the parent class is by the above definition. The code for the
methods is as follows:
79
Programmi ng Objects In Clari on
Hide:Access:Item.Init PROCEDURE
CODE
SELF.Init(Item,GlobalErrors)
SELF.FileNameValue = 'Item'
SELF.Buffer &= ITM:Record
SELF.Create = 1
SELF.LockRecover = 10
SELF.AddKey(ITM:ItemIdK,'Item Id Key',1)
SELF.AddKey(ITM:ItemNumberK,'Item Number Key',0)
Access:Item &= SELF
Hide:Access:Item.Kill PROCEDURE
CODE
PARENT.Kill
Access:Item &= NULL
While I could explain the above code in detail, it is not really necessary. You may
safely assume that all the lines starting with SELF are methods and properties
derived from FileManager. This means you are free to read the help for each.
The last line of the Init method is a bit interesting. It is making a reference to the
current object (SELF). This is where the Access:<File> class comes from that
you see in generated code.
I will grant you that these intermediate classes may not be entirely necessary, but
since they deal with setting up objects that deal with relations of files and file
management, it does make some sense to move these off to the side. Also, these
methods execute only when the program starts and quits. It also serves to keep
your source cleaner by having these classes in source files that are off to the side as
well.
There isn’t any rule in Clarion OOP that says you have to do this, it is merely a
design decision of the ABC architects.
The reason why I am showing you this is to make it clear that if you wish to add
code (embeds) for the file objects, the resulting code is always global. The best
explanation is looking at the simple code in the method. Since I earlier used the
InvDet file as an example, I’ll focus on that file.
80
ABC and OOP
Hide:Access:InvDet CLASS(FileManager)
Init PROCEDURE
Kill PROCEDURE(),DERIVED
PrimeAutoInc PROCEDURE(),BYTE,PROC,DERIVED
ValidateRecord PROCEDURE(<*UNSIGNED Failed>),|
BYTE,DERIVED
END
Hide:Relate:InvDet CLASS(RelationManager)
Init PROCEDURE
Kill PROCEDURE(),DERIVED
END
The above two classes derive from the FileManager and RelationManager
classes. Both are ABC classes. In addition, there is no TYPE attribute on either one,
so these are declarations of the classes and instances (meaning they are ready for
use).
Do not put too much stock in the names of these classes. You may never see these
anyway, as I already discussed. But the one that is interesting is the
Hide:Access:InvDet class. Notice the PrimeAutoInc method? It is there only
because of embedded code in that method. Thus, it overrides the default ABC
method.
Here is the generated code based on the code in the embed point:
Hide:Access:InvDet.PrimeAutoInc PROCEDURE
ReturnValue BYTE,AUTO
CODE
ReturnValue = PARENT.PrimeAutoInc()
IND:Sequence += (10 - (IND:Sequence % 10))
RETURN ReturnValue
Remember, this code was placed in this embed for one reason only – to change the
way ABC does auto incrementing. And modifying ABC’s behavior is the only
reason you use any ABC embed.
81
Programmi ng Objects In Clari on
Anything dealing with files declared in the dictionary (including global variables)
is in the global section, so the rule of thumb is that any embed to do with files is
global in nature. Figure 4.7 shows the global file classes.
Figure 4-7:
Local embeds
I’ve talked about global embeds, and now I’ll discuss local embeds. In fact there
are module embeds also, but there are only two for each module, one for data
declarations and the other for code. Therefore I’m going to skip any detailed
disucssion of module embeds.
For any given type of procedure, there are certain embed points that are popular.
The reason for it is that out of the box, ABC does only so much. This is by design
and these features are common enough to find in any application.
Embed points have only one purpose: to extend or replace out of the box
behavior.
82
ABC and OOP
I think most developers understand this. The burden is that they know what they
want, but sometimes get into a fog about where and how to write the code.
That is about all they need to do, and they do the above whether or not you add
embed code.
To illustrate this, suppose you create a browse from a wizard and then run it. It
works, as you can see the data in the list control. The generated code for the class
derivation would be similar to the following:
You can see that the BRW1 class only adds a little bit to its base ABC class. The
templates can detect what is on the window and they know that to make it work,
and generate the class definition accordingly.
For example, here is the complete code for the Init method:
83
Programmi ng Objects In Clari on
Notice the first line of code, the call to the parent class. That refers to the
BrowseClass in this case. This means there is functional code that is suitable to
ensure everything is in place to use objects for a browse.
The rest of the code deals with aspects that are unique to this particular browse. In
other words, it extends the base ABC functionality. This same thing happens when
you embed source code.
You really do not need to worry or concern yourself with the mechanics of
declaring/coding objects. Instead, the templates free you from this chore and
allow you to concentrate on what you need to do for this particular browse.
In essence, what is this browse doing different from what comes out of the box?
That is all you need to concern yourself with. Let ABC do the work.
Let me go over a few procedures and point out some of the common embeds.
Browse procedures
This section covers some of the common tasks in browse procedures.
84
ABC and OOP
What do these two tasks have in common? In addition, what is common about list
controls? Examine the list formatter:
Figure 4-8:
One common task is to add local variables to a list control. In the example,
LOC:ExtendedPrice is a variable used to calculate the extended price of an item.
Since this is a local variable, you can make two assumptions:
1) It will not get any data by itself. You must put the data there.
2) You will need to do something for each row of the list control. In
addition, you have no idea how the list control populates itself so the
local variable can be populated at runtime.
The first thing you must determine is how does ABC populate any list control? To
find out, you must look at the generated code.
85
Programmi ng Objects In Clari on
Queue:Browse QUEUE
CUS:Name LIKE(CUS:Name)
CUS:Number LIKE(CUS:Number)
CUS:Phone LIKE(CUS:Phone)
CUS:Contact LIKE(CUS:Contact)
CUS:CustomerId LIKE(CUS:CustomerId)
Mark BYTE
ViewPosition STRING(1024)
END
You can see that the template simply placed everything from the list in a QUEUE
structure. One could surmise that ABC somehow places data in this QUEUE and
this is what you see in the list. I should note at this point that the Clarion template
chain creates queues in the same way..
Inspecting the Help confirms this:
The data items displayed in the LIST come from a QUEUE or STRING
specified by the FROM attribute and are formatted by the parameters
specified in the FORMAT attribute (which can include colors, icons, and
tree control parameters).
Based on this, it is reasonable to guess that an embed point to add code to
populate this local variable has something to do with queues.
Figure 4-9:
86
ABC and OOP
Figure 4-10:
That does not appear to be doing much, two lines of code. You know that SELF is
whatever the current object is (see earlier for the discussion on SELF).
Looking at the dot syntax here, there are two object names. This usually indicates
the class is making use of another class. Open the INC file for the BrowseClass
(ABBROWSE in libsrc) and you will see that Fields is a reference to the
FieldsPairClass. In other words, this is an example of composition.
This method simply copies the left field to the right field as defined by the
AssignLeftToRight method:
87
Programmi ng Objects In Clari on
FieldPairsClass.AssignLeftToRight PROCEDURE
I UNSIGNED,AUTO
CODE
LOOP I = 1 TO RECORDS(SELF.List)
GET(SELF.List,I)
SELF.List.Right = SELF.List.Left
PUT(SELF.List)
END
The templates take care of coding this for you as this code shows:
BrowseClass.SetQueueRecord PROCEDURE
CODE
SELF.Fields.AssignLeftToRight
SELF.ListQueue.SetViewPosition(POSITION(SELF.View))
Think about this for a minute. There must be some mechanism for this particular
browse to know how to populate the items in the QUEUE. It does via the
BrowseClass.AddField method, which takes two parameters, the file field (or
variable) and the displayed queue field. This way the browse maintains the link
between the database and the display.
The calls to AddField happen in the setup for this browse procedure. Thus, you
may expect to find it in the ThisWindow.Init method of the generated code.
Since the browse has references to the queue fields and the file fields, that first line
of code in SetQueueRecord is the same as coding this:
InvDetList.Q.IND:Sequence = IND:Sequence
InvDetList.Q.IND:ItemNumber = IND:ItemNumber
InvDetList.Q.IND:Quantity = IND:Quantity
InvDetList.Q.IND:Cost = IND:Cost
InvDetList.Q.IND:List = IND:List
InvDetList.Q.IND:Sell = IND:Sell
InvDetList.Q.LOC:ExtendedAmount = LOC:ExtendedAmount
InvDetList.Q.IND:Print = IND:Print
InvDetList.Q.IND:InvDetId = IND:InvDetId
InvDetList.Q.IND:InvHdrId = IND:InvHdrId
ADD(InDetList.Q); !Followed by code to check for errors
The second line of code in the SetQueueRecord method sets the position value for
the QUEUE structure. ListQueue is a reference to BrowseQueue. BrowseQueue is
not, as the name would imply, another QUEUE structure. It is an interface. The
BrowseQueue interface is a defined set of behaviors that relate to the VIEW and
QUEUE that the LIST control uses.
88
ABC and OOP
The SetViewPosition method sets the POSITION of the VIEW based on the
position parameter. The VIEW structure gets the data from the file, so it is
important that the display of the QUEUE be in accord with the data in a VIEW.
What this means is that ABC is doing a task that happens in any file-based browse.
In addition, all you are doing is adding one line of code to compute a local
variable. You need do nothing else, as all you want is the computation of a local
variable in addition to populating the list box with data.
Let ABC do the grunt work, you merely add the few lines of code needed to fulfill
the design requirements of your application.
Event processing
All event processing in Clarion is conditional. In other words, when a certain
event triggers, then some code executes. ABC has many event-related embeds. You
can recognize these simply by name. The first part of the name is Take. This means
to grab hold of, or inspect or process something.
For example, consider the phrase “take a break.” This mean you are on a break or
interrupt an activity. Or take a look, meaning to inspect something. While that is
not the name of an ABC event embed, it applies. You could think of it as
inspecting an event. Thus, common event processing such as for
EVENT:NewSelection is handled by a method called TakeNewSelection.
Each different type of procedure has these Take embeds that apply. The
TakeNewSelection embed appears on any window with a list control. It can
appear on a form if there is a list control on it. Alternatively, it could appear on any
window with a drop list, drop combo or spin box control. If there is no
NewSelection event being generated, then you do not see such an embed.
This means that the templates are smart enough to give you what you might need,
but don’t give you anything you never need.
You still have the usual, general control event processing and this makes it simple
to determine if you need to use that embed. Again, the templates only give you the
event embeds that apply to that control.
89
Programmi ng Objects In Clari on
Figure 4-11:
You want the Extended Total to display any time the Quantity or Unit Price or
Discount amounts change. The code would be as follows:
ITE:Total = (ITE:Quantity * ITE:Price) - ITE:DiscountAmount
This is a simple task. However, do you want to use one embed or three or four
embeds to do this?
You could add this code to the EVENT:Accepted embed point for each control.
That is three embeds, where is the fourth? The Quantity control is a spin box, so it
could generate EVENT:NewSelection if the user presses one of the spin buttons.
Therefore, you need a copy of the code in that embed too.
Okay, that’s at least three embeds, and you can certainly call a ROUTINE to do the
calculation. That eases the maintenance as you have only one spot to change the
expression, if you ever need to.
How about using just one embed point? Before ABC, this was not possible.
Looking at the window manager embed tree, you can scroll down the list until
you find the Take embeds. They are, in alphabetical order, TakeAccepted,
TakeCloseEvent, TakeCompleted, TakeEvent, TakeFieldEvent,
TakeNewSelection, TakeRejected, TakeSelected, and TakeWindowEvent.
Out of these possible choices, you know one is correct. There are actually two
embeds that would satisfy the requirement of one embed. Your only decision is
which one is the best?
Whenever any control on a window triggers an event, any event, that is a field
event. If you notice, there is an embed with that name! It does not mention which
90
ABC and OOP
event. In addition, since a field event is an event, there is an embed with that
name too.
So, for example, when the user moves the window across the desktop, or resizes
the window, those actions trigger events. Therefore, TakeEvent may not be the best
choice. That leaves only TakeFieldEvent.
Nothing to it! That was simple, and that is about as complex as it gets. All ABC
procedures have these event processing embeds. Which embeds appear when
varies by the controls, thus the complexity of the procedure depends on the
embeds presented to you.
On a final note, look at what the ABC templates coded for you (the actual source
adds the comment “Method added to host embed code” to each line):
ThisWindow CLASS(WindowManager)
Ask PROCEDURE(),DERIVED
Init PROCEDURE(),BYTE,PROC,DERIVED
Kill PROCEDURE(),BYTE,PROC,DERIVED
Open PROCEDURE(),DERIVED
Reset PROCEDURE(BYTE Force=0),DERIVED
Run PROCEDURE(),BYTE,PROC,DERIVED
Run PROCEDURE(USHORT Number,BYTE Request),BYTE,|
PROC,DERIVED
TakeAccepted PROCEDURE(),BYTE,PROC,DERIVED
TakeFieldEvent PROCEDURE(),BYTE,PROC,DERIVED
TakeSelected PROCEDURE(),BYTE,PROC,DERIVED
END
The templates generate the proper class definition for you, based on your embeds.
This means that for any embed you use, it overrides the parent class’ method. The
code for the overridden method looks like this:
91
Programmi ng Objects In Clari on
ThisWindow.TakeFieldEvent PROCEDURE
ReturnValue BYTE,AUTO
Looped BYTE
CODE
LOOP
IF Looped
RETURN Level:Notify
ELSE
Looped = 1
END
ITE:Total = (ITE:Quantity * ITE:Price) - ITE:DiscountAmount
ReturnValue = PARENT.TakeFieldEvent()
RETURN ReturnValue
END
ReturnValue = Level:Fatal
RETURN ReturnValue
That is really all there is to know about any of these TakeEvent embed points. In
essence, if you wish to execute some code whenever a particular event happens,
regardless of which control triggered it, then use those embeds.
If you do care which control triggers an event, then use that control’s embeds. The
best way is via the window formatter. Just right-click on the control, choose
92
ABC and OOP
Embeds and all embeds that are appropriate for that control show up. This is what
an entry control’s event embeds look like:
Figure 4-12:
To give you an idea of the dynamic nature of these embeds, remember, you only
get event embed points when they are valid.
93
Programmi ng Objects In Clari on
Figure 4-13:
Figure 4-14:
94
ABC and OOP
You can see that any events that are applicable for a control show in this list. Thus
you can see that some event embeds appear only when you activate a feature. By
default, drop events are not processed until a control gets a drop ID.
From time to time, you may need to work with ABC classes directly, not just by
creating embed code. In my travels as an instructor, I have found the single most
misunderstood class to be the ErrorClass. The class itself is fine except for one
small sticking point. Developers insist on knowing what error was detected.
I believe this came about because many Clarion developers (including yours truly)
were burned badly in the past when not checking for an error when returning
from a statement. In addition, many of us are upbraided by our peers and even
clients when the need to check for an error condition is missing from code. There
are only so many times a developer is willing to go through this before the lesson
finally sinks in. Once that happens, any developer is quite reluctant to let go of a
good practice.
For years I’ve told Clarion developers to stop checking for errors themselves when
using ABC. This statement earns me looks from others like I was from Mars. Let
me repeat that. When using ABC, you do not need to check for errors. I did not
say your application does not check for errors, it does. But if you code for it, you
are checking for an error twice! When I say “checking for errors” I mean check for
the usual errors, like “record mismatch”, “record not found”, “file not open” and
the other common error conditions.
By common error conditions, I mean only the error codes Clarion knows about
out of the box, such as errors dealing with files and accessing files. If Clarion
knows about it, so does ABC. In addition, ABC knows which ones are fatal (like
error 47) and which ones are not (error 35 or 33). And it can display a proper
error message informing the user what happened. In addition, ABC displays all
the usual notification messages, as when the record was changed by another
station. This means that not only can ABC report errors, but it can determine the
degree of severity.
95
Programmi ng Objects In Clari on
The ErrorClass
The ErrorClass is simple, considering what it does. There are several major
functions the ErrorClass performs:
• Trapping errors.
• Reporting the errors if instructed to do so.
96
ABC and OOP
There are several key methods one should know very well. The first is
ErrorClass.AddErrors. This method does not add new bugs to your
application! This method installs your custom error group (including translated
error messages). This means that the ErrorClass can process your error
conditions and levels of fatality. Use this method when you wish to add your own
error messages for processing.
ErrorClass.SetFatality changes the level of fatality. It takes the error ID of the
message you wish to change as the first parameter. The ID is part of the
ErrorEntry structure. The second parameter is the new fatality level. This is
usually an equate.
The ErrorClass.Throw method may be considered the heart of the ErrorClass.
Look at this method’s code:
ErrorClass.Throw PROCEDURE(SHORT Id)
CODE
SELF.SetErrors
RETURN SELF.TakeError(Id)
There are just two lines of code, but what these two lines do is the point. For those
who want to trap specific errors in their applications, SetErrors is the method of
most interest:
ErrorClass.SetErrors PROCEDURE
CODE
SELF.SaveErrorCode = ERRORCODE()
SELF.SaveError = CLIP(ERROR())
SELF.SaveFileErrorCode = CLIP(FILEERRORCODE())
SELF.SaveFileError = CLIP(FILEERROR())
97
Programmi ng Objects In Clari on
These properties store the error state. However, when some see the PRIVATE
attribute, they get turned away. In one sense, it is a good idea to make these
private to the class as you cannot really predict if or when a new error state may
arise. It also implies that the class takes all responsibility for the proper use of any
error states. There may be a good argument that these properties should be
PROTECTED instead. While that has a ring of good sense to it, it also indicates that
the developer may have to assume more responsibility for the proper use of these
states. That is something that could defeat the design purpose of the class.
But instead of going off into hypothetical situations, lets get back to the original
problem – getting the error states, even when PRIVATE.
Since these properties are indeed PRIVATE, all that means is that these properties
are for the exclusive use of the class that defined them. Is there a method that uses
these properties, and which might be used to get at those properties? Indeed there
is! Take a look at the SubString method:
98
ABC and OOP
ErrorClass.SubsString PROCEDURE
BuildString CSTRING(2000)
ErrorPos USHORT,AUTO
CODE
BuildString = SELF.Errors.Message
Replace('%File',SELF.FileName,BuildString)
Replace('%ErrorCode',SELF.SaveErrorCode,BuildString)
IF SELF.SaveErrorCode = 90
Replace('%ErrorText',Self.SaveFileError & |
' (' & Self.SaveFileErrorCode & ')',BuildString)
ELSE
Replace('%ErrorText',Self.SaveError & |
' (' & Self.SaveErrorCode & ')',BuildString)
END
Replace('%Error',SELF.SaveError,BuildString)
Replace('%FileErrorCode',SELF.SaveFileErrorCode,BuildString)
Replace('%FileError',SELF.SaveFileError,BuildString)
Replace('%Message',SELF.MessageText,BuildString)
Replace('%Field',SELF.FieldName,BuildString)
Replace('%Procedure',SELF.GetProcedureName(),BuildString)
Replace('%Category', SELF.Errors.Category, BuildString)
IF INSTRING('%Previous',BuildString,1,1)
ErrorPos = POINTER(SELF.Errors)
IF SELF.SetId(SELF.Errors.Id,ErrorPos-1)
Replace('%Previous','',BuildString)
ELSE
Replace('%Previous',SELF.Errors.Message,BuildString)
END
GET(SELF.Errors,ErrorPos)
END
RETURN BuildString
The Replace procedure is not part of this class. It is a declared procedure in the
MAP statement in ABERROR.CLW. ErrorClass.SubsString returns the error
number and the error message. So this means that all you have to do is grab the
string and parse it? Not exactly. This method is PROTECTED. This means that it is
not open to anyone calling this method.
But wait, you are not turned away. All this means is that you may call the method
from a class derived from the ErrorClass. And if you think about this from an
encapsulation point of view, it makes perfect sense. So to make use of this method
you need to create your own class derived from ErrorClass, and then tell ABC to
use that class insetad of ErrorClass.
99
Programmi ng Objects In Clari on
INCLUDE(‘aberror.inc’),ONCE
ErrorMgr CLASS(ErrorClass),TYPE,|
MODULE(‘ErrorMgr.clw’),|
LINK(‘ErrorMgr.clw’,’_ABCLinkMode_), |
DLL(_ABCDLLMode_)
GetErrStr PROCEDURE, STRING,VIRTUAL
GetErrNum PROCEDURE, LONG, VIRTUAL
END
There you have it, a simple derived error manager. Of course, the code for the
methods in ErrorMgr.clw now follows.
MEMBER
MAP
END
INCLUDE(‘ErrorMgr.inc’),ONCE
ErrorMgr.GetErrStr PROCEDURE
CODE
RETURN SELF.SubsString() !Return entire error string
100
ABC and OOP
ErrorMgr.GetErrNum PROCEDURE
CurrentString CSTRING(2000)
CurPos LONG
EndPos LONG
RetVal LONG
CODE
CLEAR(RetVal)
CurrentString = SELF.SubsString()
CurPos = INSTRING(‘(‘,CurrentString,1,1)
IF CurPos
CurPos += 1
EndPos = INSTRING(‘)’,CurrentString,1,CurPos)
IF EndPos
EndPos -= 1
RetVal = CurrentString [ CurPos : EndPos ]
END
RETURN RetVal
To use this class, both files must be saved in libsrc. Either start Clarion or press
Refresh Application Builder Class Information in any class dialog to load the class
in the reader.
Figure 4-15:
101
Programmi ng Objects In Clari on
If you wish to see this class in the reader, press Application Builder Class Viewer.
The ErrorMgr class should look like this:
Figure 4-16:
You can see the two methods in this class as well as all the properties and method
inherited from the ErrorClass. Right-click on the list and choose Show Symbol
Key for what the colors mean.
102
ABC and OOP
Figure 4-17:
You can see that the default error manager is GlobalErrors. This derives from
ErrorClass and is the template default and is thus disabled. Uncheck the Use
Default ABC ErrorClass box. The Use Application Builder Class box enables as does
the Base Class drop list.
Select ErrorMgr from the drop list. Press OK until you are back at the application
tree.
Figure 4-18:
This becomes the new class that forms the GlobalErrors class, which is local to
your application. More importantly, any code, which includes source embeds
103
Programmi ng Objects In Clari on
already present, does not have to change as the class instance (that is, the class
object) name used in your application remains unchanged.
104
ABC and OOP
NOTE In Clarion 6, there are two new methods that do return the error
code and error message.
This is why I always frown when someone insists on editing the shipping classes.
The next time they install a patch or newer version, they need to make the same
105
Programmi ng Objects In Clari on
edits again. One may get clever to minimize any impact of editing ABC classes,
but it is still extra work and you really do not have to bother.
106
Chapter 5
CODING OBJECTS
This chapter covers coding objects. In previous chapters I discussed OOP theory
and how ABC does OOP. Now it’s time to take generated legacy style (procedural)
code and convert it into a class.
Generated legacy code (sometimes hand code) often results in a lot of code
duplication. Often this is a great opportunity to take that code and make it into a
class in its own right. The goal of this chapter is simply to show how to convert
working code to a class. Initially, such a class won’t have any template support.
That subject is covered in the next chapter.
Design Considerations
The first thing you should have in mind when writing a new class is a simple
question. Ask yourself, “Could I reuse this code in other applications?” If you
answered in the affirmative, then you have a valid reason. Another good reason
would be if you find yourself using routines, or a ROUTINE that is repeated
throughout your application, but with minor changes to each.
The next consideration is whether you have an example of working code. If you
do, then life is easy. There really is not much to change in this aspect. If you do not
having an example of working code, this just means that you should pay more
attention to the design. This chapter uses a working example that any Clarion
developer should already have.
107
Programmi ng Objects In Clari on
This example is the Invoice example application that ships with Clarion. Load
this application and open the window formatter for the procedure
BrowseAllOrders. If you right-click on the list box, then choose List box Format...
you see the window in Figure 5.1, and if you click on the Appearance tab you will
see Figure 5.2:
Figure 5-1:
108
Coding Objects
Figure 5-2:
These two views are enough to start with. The tree list displays colors and icons.
At runtime, this is what you see:
Figure 5-3:
This is a functional tree list, and each item in the list has edit procedures attached.
The code generated by the Relation Tree control template is written into routines
for each file in the tree list. By that I mean they are the same routines, with just
minor differences. Thus, this is a good candidate for a new class. (As an aside,
Clarion as shipped does not have a relation tree object. This includes ABC, and it is
almost identical to the one produced by the Clarion template chain.)
109
Programmi ng Objects In Clari on
The goal is simple – create a class that has the same functionality as this one.
The ABC and Clarion/legacy versions of the relation tree both work, but they also
both rely on the templates to manage all of the code. The only real differences are
that the ABC version makes some ABC method calls to open files, get the data, etc.
Both template chains use routines heavily, and depending on the files in the tree,
produce similarly named routines.
If you have to make any changes to how the relation tree functions, you don’t
want all of that code. The reason is that such code requires you to be a template
developer as well as a Clarion developer. That tells you right away that there is
little or no code re-use, which one of the main goals of OOP.
When you start thinking of how to write better and tighter code, you need to
think in abstract concepts.
Any class should work equally well in hand-coded projects, not just template
driven apps. The only difference is that hand coding developers must handle all
the declarations and instantiation themselves. Other than that, there should never
be any significant differences between a hand coded project and an application;
the end result should be the same.
Designs
To convert an existing design to a new one, you must have a grasp of the old
design. In the case of the relation tree, this can be broken down into two parts,
editing and navigation. Therefore, I’ve made some simple charts of each of the
major components.
110
Coding Objects
Navigation
This illustration shows how the navigation works currently.
Figure 5-4:
111
Programmi ng Objects In Clari on
Edits
This illustration shows the current edit functions.
Figure 5-5:
Where do you start with something like this? Let’s examine the generated code. To
ease this step, you should have BrowseAllOrders in its own module. That way
you don’t see anything that is “non-tree”. The easiest way is to choose from the
main menu, Application | Repopulate Modules. Choose 1 procedure per module
and then regenerate the source. Open up any editor and load the module with the
112
Coding Objects
tree code in it. You can see what the module name is by either opening the
procedure dialog or select the module view.
You’ll need to open a new source file too. This is where the class code goes. Call
the file CBTree.inc (CB meaning “Clarion Book”) and save it in the libsrc
directory. . You’ll be switching between the open module and the CBTree file.
Add the following comment on line one:
!ABCIncludeFile(CB)
This comment is seen by the ABC class reader. The (CB) portion of the comment
means that this class isn’t linked in by any project that does not use this tree class.
You may enter anything as a parameter. If you left the parameter off, then all
projects would link in the class. I’ll explain how this works later.
Let’s examine the module code. Near the top you see a few data declarations and
two queue structures. You’ll concentrate on the QUEUE structures first.
Here are the generated QUEUEs from ABC:
Simply copy them as-is and paste in the code in the CBTree.inc file.
Change the code so the source looks as follows in your CBTree.inc file:
113
Programmi ng Objects In Clari on
!ABCIncludeFile(CB)
I’m not too wild about the use of the colon in data labels, except for field labels
separating them from the file prefix. Thus, they are removed. I think this makes
the code easier to read, as well as write. In addition, the labels of the structures
could stand shortening.
I’ve also taken the liberty of adding more comments. In this book, the comments
are wrapped for readability, but in the accompanying code, they are on one line. I
know “documentation” is a four-letter word in most programmers’ vocabularies,
however I feel this is a necessary evil.
The QRelTree is a QUEUE that controls the display of the tree list. The presence of
records with a level greater than the current level tells the list box that there is a
child file loaded. This number can be positive or negative (expanded or
collapsed). The LoadedQ keeps track of which child file is loaded, if any.
114
Coding Objects
linked to your application. To follow the ABC example, name this variable
_CBLinkMode_. And lastly, add the DLL attribute with its flag parameter of
_CBDLLMode_. To finish this, add END to the next line and align it with CLASS.
Your source should appears as follows (wrapped here to provide readability, but
the entire CLASS portion of the declaration can be on one line if you prefer):
RelationTree CLASS,MODULE('CBTree.clw'),|
LINK('CBTree.clw',_CBLinkMode_),|
DLL(_CBDLLMode_)
END
As a general tip, when you code a structure, always code the END statement right
away. Then move up one line and press enter to open a new line. This way, you
won’t forget to add the END statement later, which leads to compiler errors.
Adding Properties
The next step is to add some properties. With existing code, this is not difficult.
Go back to the generated code and you’ll see some data definitions before and
after the generated queues. Copy and paste these declarations before the END
statement of the CLASS. In keeping with the naming style, remove the REL1:
portion of the labels. I’ve added some comments where needed.
Your definition should now look like this:
RelationTree CLASS,MODULE('CBTree.clw'),|
LINK('CBTree.clw',_CBLinkMode_),|
DLL(_CBDLLMode_)
SaveLevel BYTE,AUTO ! Current level
Action LONG,AUTO ! Edit action
CurrentLevel LONG ! Current loaded level
CurrentChoice LONG ! Current highlighted record
NewItemLevel LONG ! Level for a new item
NewItemPosition STRING(1024) ! POSITION of new record
LoadAll LONG !Flag to load all levels
END
The class is starting to take shape. However, there are at least two properties
missing. What about the QUEUE structures? You cannot legally define a QUEUE
structure in a CLASS, which is why they are declared outside of the CLASS.
After the last property declaration, open a new line and add the references to the
two QUEUE declarations. There are other properties needed and they are listed
here:
115
Programmi ng Objects In Clari on
Some of these properties are PROTECTED. This is so that they can be accessed by
derived classes, but not by any code outside of methods belonging to this class
family. If only this base class should have access to them, then you would add the
PRIVATE attribute to these properties.
If you really are not sure if a particular member should be PROTECTED, then use
the PRIVATE attribute. Later as needs arise, you could change the attribute to
PROTECTED or add new methods to this CLASS that return the values of these
properties. The actual properties are still encapsulated regardless of what you do.
There you have the first method. This method automatically executes when this
class is instantiated, regardless of how it is instantiated.
Since a constructor now exists, a destructor would not be a bad idea. Make a new
line and declare the destructor as follows:
Destruct PROCEDURE
The destructor is now defined. Its purpose is to “undo” what the constructor does.
This method automatically executes when this class is destroyed, regardless how it
is destroyed.
The code for both of these methods follows shortly.
You may be wondering where you’ll get the code for the remaining methods. In
the generated code, do a search for ROUTINE. You are interested only in the
REL1::<whatever> routines.
116
Coding Objects
For now, you are only interested in the routine labels. You will be interested in the
code, but that step is for later. You are not interested in any method parameters, at
least not yet. To get things started, simply copy the labels and paste them under
the last method, which should be Destruct. Don’t worry about the names for the
moment, nor the prototypes.
You’ll soon bump into the routines that are specific to a file, such as
REL1::Load:Customers and REL1::Format:Customers. For simplicity’s sake,
just copy each of these as is. Do the same for the rest of these routines until you
get to the next file, REL1::Load:Orders. You skip this and all other such labels
with file names in them, since you only need one set of file-specific routines
(which you will convert to work with any file). Continue to copy the routine
names that do not refer to any file.
117
Programmi ng Objects In Clari on
The next step is to remove the REL1:: portion of each method label, using search
and replace or whatever other method you like (some editors, though not the
118
Coding Objects
Clarion editor, let you do column deletes. After that step, ensure each method has
the PROCEDURE prototype.
The source should now appear as follows:
RelationTree CLASS,MODULE('CBTree.clw'),|
LINK('CBTree.clw',_CBLinkMode_),|
DLL(_CBDLLMode_)
SaveLevel BYTE,AUTO ! Current level
Action LONG,AUTO ! Edit action
CurrentLevel LONG ! Current loaded level
CurrentChoice LONG ! Current highlighted record
NewItemLevel LONG ! Level for a new item
NewItemPosition STRING(1024) ! POSITION of new record
LoadAll LONG ! Flag to load all levels
QRT &QRelTree ! Reference to QRelTree type
LDQ &LoadedQ ! Reference to the LoadedQ type
LC SIGNED,PROTECTED ! FEQ for list control
BaseFile &FILE,PROTECTED ! Base file in passed VIEW
WinRef &WINDOW,PROTECTED ! Reference to current window
VCRRequest LONG(0) ! VCR action
InsertButton SIGNED ! FEQ for Insert Button
ChangeButton SIGNED ! FEQ for Change Button
DeleteButton SIGNED ! FEQ for Delete Button
NextParent PROCEDURE
PreviousParent PROCEDURE
NextLevel PROCEDURE
NextSavedLevel PROCEDURE
PreviousSavedLevel PROCEDURE
PreviousLevel PROCEDURE
NextRecord PROCEDURE
PreviousRecord PROCEDURE
AssignButtons PROCEDURE
Load:Customers PROCEDURE
Format:Customers PROCEDURE
LoadLevel PROCEDURE
UnloadLevel PROCEDURE
AddEntry PROCEDURE
EditEntry PROCEDURE
RemoveEntry PROCEDURE
UpdateLoop PROCEDURE
AddEntryServer PROCEDURE
EditEntryServer PROCEDURE
119
Programmi ng Objects In Clari on
RemoveEntryServer PROCEDURE
RefreshTree PROCEDURE
ContractAll PROCEDURE
ExpandAll PROCEDURE
END
The class is taking on more form now. There are two method names that still need
changing. These are the ones labeled Load:Customers and Format:Customers.
Set these aside for now as these two methods will require some thought. More on
that later, but save your work so far.
MAP
END
The ONCE attribute is really not required as this is the only time the header
definitions are included. However, using the ONCE attribute never hurts, and it’s a
good habit to have.
120
Coding Objects
1) Go back to the INC file and highlight all of the methods declared in it.
Copy the labels (names) of the methods and the prototypes.
2) Copy them to the clipboard.
3) Switch to the CLW file.
4) Paste the declarations at the bottom of the source.
5) Open the search and replace feature of your editor. This works best if
you have one space in column one and your editor can do column
search and replace. Search for the single space and replace it with
RelationTree. (Don’t forget the ending period!)
121
Programmi ng Objects In Clari on
MAP
END
INCLUDE('CBTree.inc'),ONCE
RelationTree.NextParent PROCEDURE
RelationTree.PreviousParent PROCEDURE
RelationTree.NextLevel PROCEDURE
RelationTree.NextSavedLevel PROCEDURE
RelationTree.PreviousSavedLevel PROCEDURE
RelationTree.PreviousLevel PROCEDURE
RelationTree.NextRecord PROCEDURE
RelationTree.PreviousRecord PROCEDURE
RelationTree.AssignButtons PROCEDURE
RelationTree.Load:Customers PROCEDURE
RelationTree.Format:Customers PROCEDURE
RelationTree.LoadLevel PROCEDURE
RelationTree.UnloadLevel PROCEDURE
RelationTree.AddEntry PROCEDURE
RelationTree.EditEntry PROCEDURE
RelationTree.RemoveEntry PROCEDURE
RelationTree.UpdateLoop PROCEDURE
RelationTree.AddEntryServer PROCEDURE
RelationTree.EditEntryServer PROCEDURE
RelationTree.RemoveEntryServer PROCEDURE
RelationTree.RefreshTree PROCEDURE
RelationTree.ContractAll PROCEDURE
RelationTree.ExpandAll PROCEDURE
6) Open a new line under the first method (the Construct method).
7) Press SPACE twice and then enter CODE and then press enter.
8) Copy the CODE line (with its CR/LF) and paste after each method listed.
122
Coding Objects
MAP
END
INCLUDE('CBTree.inc'),ONCE
RelationTree.NextParent PROCEDURE
CODE
RelationTree.PreviousParent PROCEDURE
CODE
RelationTree.NextLevel PROCEDURE
CODE
RelationTree.NextSavedLevel PROCEDURE
CODE
RelationTree.PreviousSavedLevel PROCEDURE
CODE
RelationTree.PreviousLevel PROCEDURE
CODE
RelationTree.NextRecord PROCEDURE
CODE
RelationTree.PreviousRecord PROCEDURE
CODE
RelationTree.AssignButtons PROCEDURE
CODE
RelationTree.Load:Customers PROCEDURE
CODE
RelationTree.Format:Customers PROCEDURE
CODE
RelationTree.LoadLevel PROCEDURE
CODE
RelationTree.UnloadLevel PROCEDURE
CODE
123
Programmi ng Objects In Clari on
RelationTree.AddEntry PROCEDURE
CODE
RelationTree.EditEntry PROCEDURE
CODE
RelationTree.RemoveEntry PROCEDURE
CODE
RelationTree.UpdateLoop PROCEDURE
CODE
RelationTree.AddEntryServer PROCEDURE
CODE
RelationTree.EditEntryServer PROCEDURE
CODE
RelationTree.RemoveEntryServer PROCEDURE
CODE
RelationTree.RefreshTree PROCEDURE
CODE
RelationTree.ContractAll PROCEDURE
CODE
RelationTree.ExpandAll PROCEDURE
CODE
Be sure to save your work at this point. You now have a class that can compile
clean. It still does not do anything (not yet). But you should let the compiler
check your work at this stage.
A New Project
Start a new project. You can do this by choosing File | New | Project from the menu,
or use the pick list dialog and make a new project from there. It does not matter
what you name the project, but I suggest you name it something meaningful to
you, like RTTest. A description is optional, but I would suggest something like
Relation Tree Class Test. Enter a name for the file, like RTCTest.clw. The EXE
124
Coding Objects
name is filled in for you. Press OK to save your choices and the project tree
displays.
Figure 5-6:
Next you’ll need to set some project variable definitions, which are used by the
compiler and linker to correctly build your project. If a define is not explicitly set
to => off, it is on. Press Properties and then choose the Defines tab. Be sure to
add the following variables and their settings:
Figure 5-7:
125
Programmi ng Objects In Clari on
Press OK when you are done and return to the project window. Highlight
RCTTest.clw and press Edit. You should now have an empty source file. Add code
to match the following figure:
PROGRAM
MAP
END
INCLUDE('CBTree.inc'),ONCE
CODE
You can now press the compile button and it should make the EXE. The EXE
won’t do anything as there isn’t any code to tell it what to do. This comes later.
The test at this stage is that it compiles clean. If you did not get a clean compile,
go back and review the previous steps or use the errors window to fix the
problem. Usually, the problem is a simple typo.
Summary
What you just did was a simple exercise that you can use to code a skeletal CLASS
structure. There are two purposes to this exercise:
9) Getting you more familiar with a CLASS structure by easily hand coding
it.
10) Using the labels of the code you wish to convert to a CLASS, thus
increasing your familiarity with the existing code.
Generic class
The main point to keep in mind when coding classes is that nothing is hard coded
as far as the data it manipulates. Where the original source uses hard coded
values, you will use variables and reference variables.
If you look at the generated code from the example Invoice application, there are
many references to ?RelTree, the field equate for the list control. What is needed
is a way to tell this class which list control it should recognize.
126
Coding Objects
This is what the LC property is for. LC means List Control. Since it really holds
the field equate for the list, the data type is SIGNED. Finally, since only this class or
derived classes should deal with this value, LC has the PROTECTED attribute.
NOTE It really does not matter what order properties and methods list
themselves in a class structure. If you group them together, then you
have an easier time maintaining the class.
Now that the class is taking shape, let’s address the constructor and destructor.
127
Programmi ng Objects In Clari on
RelationTree.Destruct PROCEDURE
CODE !Automatic destructor
IF ~SELF.QRT &= NULL !If property is not null
FREE(SELF.QRT) !Free queue's resources
DISPOSE(SELF.QRT) !and dispose of it
END
IF ~SELF.LDQ &= NULL !If property is not null
FREE(SELF.LDQ) !Free queue's resources
DISPOSE(SELF.LDQ) !and dispose of it
END
IF ~SELF.WinRef &= NULL !Reference to window still valid
SELF.WinRef &= NULL !Remove the reference
END
You now have two methods that automatically do the “grunt” work. If you notice
in the Destruct method, code exists to dereference the other properties.
However, the constructor does not assign these references. It can’t. Constructors
cannot take parameters to pass in the Window, List and File parameters. Thus
another method that can take these parameters handles this.
Theoretically, the constructor could call a method to do the additional reference
assignments, but again, how can it know about the current window or file
structure? It can’t. This is where the Init method comes in.
128
Coding Objects
Init and Kill, by themselves are nothing special, they are not reserved words
nor do they have any special meaning in Clarion.
129
Programmi ng Objects In Clari on
method code. To make this simple, you may scroll to the bottom of the file and
paste it there. Or make it the first method shown. Another standard is the same
order as declared. Or alphabetical. In other words, it really does not matter where
you place the code (just don’t paste it inside another method!).
Once you have pasted the code where you want, remove the LONG, PROC, and
VIRTUAL attributes. The only thing you need in the method code is the label of the
method and its prototype. Attributes and return values are not legal in the method
code section. Open a new line and add the word CODE.
Fill in the rest of the code so your method appears as follows:
RelationTree.InitTree PROCEDURE(*WINDOW xWin, |
SIGNED xList,|
*VIEW xFile)
RetVal LONG,AUTO
CODE
RetVal = False
SELF.WinRef &= xWin !Reference to window
IF SELF.WinRef &= NULL
RetVal = True
RETURN RetVal
END
SELF.LC = xList !Store the FEQ
SELF.BaseFile &= xFile !Set primary file
IF SELF.BaseFile &= NULL
RetVal = True
RETURN RetVal
END
SELF.WinRef $ SELF.LC{PROP:From} = SELF.QRT !list gets data from?
SELF.RefreshTree() !and populate it with data
RETURN RetVal
Notice the use of SELF. This means “whatever the current object is”.
The WinRef property is a reference to the current window. After the reference
assignment, it is tested for “nullness.” If for some reason it is NULL, the method
returns to the caller with a return value.
The BaseFile property is a reference to the passed primary file. This is also tested
for “nullness” and if it is NULL, the code quits this method and returns a value.
The list control field equate is assigned to the LC property. The QRT property
(assigned by the Construct method) is where the list control gets its data. Next
comes a call to RefreshTree() (discussed later).
130
Coding Objects
The final line of code returns a value, in this case, a zero or false value, meaning
no errors.
I should point out that the variable, Retval, declared in this method is local to
this method. That means it is for the use of this method only, despite any rules of
derivation. Variables declared in this fashion are considered implicitly PRIVATE.
Now is the time to bring up why this is a VIRTUAL method. This method expects
to be overridden. When you derive from this class, you may want to override or
extend this method. In this case simply write your code. At the appropriate time
in the execution sequence, you have the option of calling PARENT.InitTree in
your derived method. Or don’t call the Parent method if you want to completely
replace the method’s functionality.
Remember, this class calls its derived children instead of its own methods to do
work. This is the power of virtual methods. You have the choice of extending a
method by calling the parent in your overridden method, or not calling it at all if
you are replacing all functionality of the parent method. It is up to you.
Of course, having a template wrapper to write the derived virtual method code for
you, according to the options selected in the template, is the best way to proceed.
This class design allows for such use, and a template wrapper is the subject of the
next chapter.
You could copy more code here from the generated source, but it would limit
what you could do with this class. For example, you could add this code:
131
Programmi ng Objects In Clari on
However, you can see this would limit the list control in its placement, appearance
and size. There is nothing wrong with this code per se, and it may indeed be fine
for many list controls. There is nothing really wrong with doing this anyway, as
any derived method may override these settings.
However, this is best done in a derived child class, with templates writing the
above, based on what is known at the time. In other words, the derived class, not
the base class, shoudl create code that is specific to that particular list control.
The point is that you can think up many useful things to do with code, but this is
a base class, so minimal functionality is always best. In this example, only the
code that must always be there is present. The property statements above could be
there, but none are required. This does not mean you may not add functionality
like this later. All without losing any existing functionality.
You must resist the temptation to do too much. When coding base classes like
this, only the irreducible minimum code should be in it. Even if this means a loss
in functionality! I know how strange that sounds, but keep in mind the derived
132
Coding Objects
class is where the missing functionality is. The more you derive, the more specific
your code gets.
That is a no-brainer. Nice and simple. However, no Clarion developer would use
only that code as it is dangerous. What if there is an error during the open? And
what type of error? Not all errors with opening files are fatal. Opening a file gets
complex in a hurry, doesn’t it?
Many Clarion developers can write code that can test for and handle error
conditions, but now you are no longer dealing with a method that opens files, as
error checking and handling doesn’t necessarily have anything to do with a
method that opens a file. It would make more sense to call a method that does
error checking for you. To strengthen this argument, any classes that trap and
report errors must be able to handle other errors, not just from the limited
number of errors that could arise from attempting to open a file. Thus, a call to a
class that handles errors makes sense.
You still get your error checking, but you keep distinct functionality separate.
MyFileClass.Open PROCEDURE(*FILE MyFile)
CODE
OPEN(MyFile)
MyErrorClass.ErrorCheck() !Was there an error?
The bottom line is you should always keep code painfully simple, even at the
price of omitting code that could be useful. This useful code may be best suited to
live in another class, perhaps derived, perhaps another base class (or derived from
another base class). A nice bonus for you is that maintaining such code is easier as
you do not have to wade through lines and lines of code unrelated to the task at
hand. If you wish to do more with file management, then you know which class to
go to. The same goes if you have a nifty idea about handling errors.
133
Programmi ng Objects In Clari on
The rest of this chapter takes you through the other methods in the class. Each
method’s purpose is discussed, followed by the code.
Following each method is a brief discussion of what the code does. The source
code that comes with this book is fully commented, but the comments are
omitted in the following pages.
RefreshTree()
RefreshTree is a method that is called often in the class. Its main purpose is to
load the tree with fresh data. You would call this method anytime you want or
need to re-load the tree.
RelateTree.RefreshTree PROCEDURE()
CODE
LoadLevel()
This is a virtual stub method.
134
Coding Objects
UnloadLevel()
This is a virtual stub method.
NextParent()
This method determines the next parent level of the tree:
RelationTree.NextParent PROCEDURE()
CODE
IF ~SELF.WinRef{PROP:Active}
SELF.WinRef{PROP:Active} = True
END
GET(SELF.QRT,CHOICE(SELF.LC))
IF ABS(SELF.QRT.Level) > 1
SELF.SaveLevel = ABS(SELF.QRT.Level) - 1
SELF.NextSavedLevel()
END
If the window is not the active window, then the window containing the tree
control is set to the active window. The code then gets a record from the QRT
structure based on the current choice. If the level is greater than 1, take 1 away
and save that value in the SavedLevel property. Then call the NextSavedLevel
method.
PreviousParent()
This method determines the previous parent level based on the current level:
RelationTree.PreviousParent PROCEDURE()
CODE
IF ~SELF.WinRef{PROP:Active}
SELF.WinRef{PROP:Active} = True
END
GET(SELF.QRT,CHOICE(SELF.LC))
IF ABS(SELF.QRT.Level) > 1
SELF.SaveLevel = ABS(SELF.QRT.Level) - 1
SELF.PreviousSavedLevel()
END
First, the code determines if the window is active and if not makes it active. Next,
it gets the record from QRT based on the current choice (highlighted row).
If the level is greater than 1, it subtracts one from whatever the current value is
and puts it into the SaveLevel property. The PreviousSavedLevel method is
called.
135
Programmi ng Objects In Clari on
The significance of 1 means that the current level is not a root item. Subtracting 1
from the current level yields, by definition, a parent node.
NextLevel()
This method finds the next level that is the same as the current one:
RelateTree.NextLevel PROCEDURE
CODE
IF ~SELF.WinRef{PROP:Active}
SELF.WinRef{PROP:Active} = True
END
GET(SELF.QRT,CHOICE(SELF.LC))
SELF.SaveLevel = ABS(SELF.QRT.Level)
SELF.NextSavedLevel()
The code first determines if the window is active and if not makes it active. Next,
it gets the record from QRT based on the current choice (highlighted row). It then
puts the current level value into the SaveLevel property. The NextSavedLevel
method is called.
PreviousLevel()
This method finds the previous level that matches the current level:
RelateTree.PreviousLevel PROCEDURE
CODE
IF ~SELF.WinRef{PROP:Active}
SELF.WinRef{PROP:Active} = True
END
GET(SELF.QRT,CHOICE(SELF.LC))
SELF.SaveLevel = ABS(SELF.QRT.Level)
SELF.PreviousSavedLevel
The code first determines if the window is active and if not makes it active. Next,
it gets the record from QRT based on the current choice (highlighted row).
It then puts the current level value into the SaveLevel property. The
PreviousSavedLevel method is called.
NextSavedLevel()
This method determines the next level that matches the current level. It reads the
QUEUE structure to determine this.
136
Coding Objects
RelateTree.NextSavedLevel PROCEDURE
SavePointer LONG,AUTO
CODE
LOOP
LOOP
GET(SELF.QRT,POINTER(SELF.QRT) + 1)
IF ERRORCODE()
RETURN
END
WHILE ABS(SELF.QRT.Level) > SELF.SaveLevel
IF ABS(SELF.QRT.Level) = SELF.SaveLevel
SELECT(SELF.LC,POINTER(SELF.QRT))
RETURN
END
SavePointer = POINTER(SELF.QRT)
SELF.LC{PROPLIST:MouseDownRow} = SavePointer
SELF.LoadLevel
GET(SELF.QRT,SavePointer)
END
There are two LOOP structures. The first or outer LOOP encompasses the entire
method. The next, inner LOOP first checks to see if the Level value in the QUEUE is
greater than the SaveLevel property value. This is done with the WHILE
statement. If the Level value is greater than SaveLevel, then another iteration of
the LOOP executes. During any iteration, if there is no record found by matching
key available in the QUEUE (which raises an ErrorCode value), the method quits.
If the value of the Level is not greater than the SavedLevel value, the next line
determines if the levels match. Notice the use of the ABS function here. Since the
Level value can be negative (indicating a collapsed node), this function forces a
positive (without saving this value). The highlight bar is then placed on the row in
the list control and the method quits.
If the Level value is less than the SavedLevel value, the current pointer value is
saved and then passed to the list control. Next, the LoadLevel method is then
called. After returning from this method, the code retrieves any changes to the
current QUEUE record.
PreviousSavedLevel()
This method determines the previous level that matches the current level. It reads
the QUEUE structure to determine this:
137
Programmi ng Objects In Clari on
RelateTree.PreviousSavedLevel PROCEDURE
SaveRecords LONG,AUTO
SavePointer LONG,AUTO
CODE
LOOP
LOOP
GET(SELF.QRT,POINTER(SELF.QRT) - 1)
IF ERRORCODE()
RETURN
END
WHILE ABS(SELF.QRT.Level) > SELF.SaveLevel
IF ABS(SELF.QRT.Level) = SELF.SaveLevel
SELECT(SELF.LC,POINTER(SELF.QRT))
RETURN
END
SavePointer = POINTER(SELF.QRT)
SaveRecords = RECORDS(SELF.QRT)
SELF.LC{PROPLIST:MouseDownRow} = SavePointer
SELF.LoadLevel()
IF RECORDS(SELF.QRT) <> SaveRecords
SavePointer += 1 + RECORDS(SELF.QRT) - SaveRecords
END
GET(SELF.QRT,SavePointer)
END
There are two LOOP structures. The first or outer LOOP encompasses the entire
method. The next, inner LOOP first checks to see if the Level value in the QUEUE is
greater than the SaveLevel property value. This is done with the WHILE
statement. If Level is greater than SaveLevel, then another iteration of the LOOP
executes. During any iteration, if there is no record by matching key available in
the QUEUE (which raises an ErrorCode value), the method quits.
If the value of the Level is not greater than the SavedLevel value, the next line
determines if the levels match. Notice the use of the ABS function here. Since the
Level value can be negative, this function forces a positive (without saving this
value). The highlight bar is then placed on the row in the list control and the
method quits.
If the Level value is less than the SavedLevel value, the current pointer value is
saved and then passed to the list control. Also saved is how many records are in
the QUEUE. The LoadLevel method is then called. After returning from this
method, the code determines if LoadLevel added any new records to the QUEUE. If
so (as the number of records in the QUEUE won’t match the value of
SavedRecords), it increments the pointer. Finally the code gets the current QUEUE
record based on the pointer key value.
138
Coding Objects
NextRecord()
This method finds the next record in the list box and moves the highlight to it.
RelateTree.NextRecord PROCEDURE
CODE
IF ~SELF.WinRef{PROP:Active}
SELF.WinRef{PROP:Active} = True
END
SELF.LoadLevel
IF CHOICE(SELF.LC) < RECORDS(SELF.QRT)
SELECT(SELF.LC,CHOICE(SELF.QRT) + 1)
END
The code first determines if the window is active and if not, makes it active. It
then calls the LoadLevel method.
After returning, it performs a check to see if the highlighted row is less than the
number of record in the QUEUE. If so, it increments the pointer by one and this
value is sent to the list control, which has the effect of moving the highlight one
row down.
PreviousRecord()
This method finds the previous record in the list and moves the pointer to it:
RelateTree.PreviousRecord PROCEDURE
SaveRecords LONG,AUTO
SavePointer LONG,AUTO
CODE
IF ~SELF.WinRef{PROP:Active}
SELF.WinRef{PROP:Active} = True
END
SavePointer = CHOICE(SELF.LC)-1
LOOP
SaveRecords = RECORDS(SELF.QRT)
SELF.LC{PROPLIST:MouseDownRow} = SavePointer
SELF.LoadLevel
IF RECORDS(SELF.QRT) = SaveRecords
BREAK
END
SavePointer += RECORDS(SELF.QRT) - SaveRecords
END
SELECT(SELF.LC,SavePointer)
The code first determines if the window is active and if not, makes it active. Next,
it saves the value of the current position minus one.
139
Programmi ng Objects In Clari on
A LOOP structure then determines the number of records in the QUEUE and this
value is saved. The SavePointer value is then used to position the highlight. The
LoadLevel method is then called.
After returning from this method, the code checks to see if the number of records
still matches the SavedRecords value. If so, then the LOOP terminates. If not, the
code increments the pointer by adding the difference of the number of records
now present to the previous value and another iteration of the LOOP is performed.
After the LOOP terminates, the new SavePointer value is passed to the list
control, selecting the matching row based on the SavePointer value.
These are more methods that have no code in them. These are placeholders for
virtual methods. At the base class level, there is no way to know precisely how to
code them as they require information specific to each use. In other words, once
this class is locally derived, then there is enough information to actually code the
methods.
AssignButtons()
This method is to assign the navigation buttons from the toolbar, if a toolbar is
present in the application. The resulting code should be similar to this:
RelationTree.AssignButtons PROCEDURE
CODE
The templates discussed in the next chapter write the method code in the child
class.
The following three methods control the editing of the highlighted item.
AddEntryServer()
EditEntryServer()
RemoveEntryServer()
For each method, there is an associated button control (Insert, Change, Delete). If
the control is either hidden or disabled, then the method should RETURN.
140
Coding Objects
Since the templates fill in the data about the back end VIEW structure, the code
needed for each is determined by the templates. These methods are called from
the edit methods, as follows.
Edit Methods
There must be a way to edit the highlighted item in the tree as well. To keep
edit tasks simple, this class uses just a few methods. The following methods set
the edit action.
AddEntry()
RelationTree.AddEntry PROCEDURE
CODE
SELF.Action = InsertRecord
SELF.UpdateLoop()
EditEntry()
RelationTree.EditEntry PROCEDURE
CODE
SELF.Action = ChangeRecord
SELF.UpdateLoop()
DeleteEntry()
RelationTree.DeleteEntry PROCEDURE
CODE
SELF.Action = DeleteRecord
SELF.UpdateLoop()
Each of the above methods sets the Action property and then calls the
UpdateLoop method.
141
Programmi ng Objects In Clari on
UpdateLoop()
This method is responsible for any edits and any toolbar navigation. If no edits
are enabled and there are no toolbar navigation buttons, this method is never
called.
RelationTree.UpdateLoop PROCEDURE
CODE
LOOP
SELF.VCRRequest = VCR:None
SELF.LC{PROPLIST:MouseDownRow} = CHOICE(SELF.LC)
CASE SELF.Action
OF InsertRecord
SELF.AddEntryServer()
OF DeleteRecord
SELF.RemoveEntryServer()
OF ChangeRecord
SELF.EditEntryServer()
END
CASE SELF.VCRRequest
OF VCR:Forward
SELF.NextRecord()
OF VCR:Backward
SELF.PreviousRecord()
OF VCR:PageForward
SELF.NextLevel()
OF VCR:PageBackward
SELF.PreviousLevel()
OF VCR:First
SELF.PreviousParent()
OF VCR:Last
SELF.NextParent()
OF VCR:Insert
SELF.PreviousParent()
SELF.Action = InsertRecord
OF VCR:None
BREAK
END
END
This code calls the actual method to do the edits. It also responds to the VCR
control events, since the tree could be navigated by the toolbar VCR controls.
Notice the VCRRequest is set to VCR:None. This is an equate for zero. The three
possible edit methods return from their calls, with a SELF.VCRRequest. Thus, the
CASE statement for it. SELF.VCRRequest is a property of this class; be sure to add
it to the declaration as a LONG(0) – the initial value of zero is important.
142
Coding Objects
Another note about this VCR business. In the INC file, be sure to add this line
before any data declarations:
INCLUDE('ABTOOLBA.INC'),ONCE
This file contains all of the equates needed to make a clean compile. However, if
you try it at this time, you get a link error about the ASCII driver not found. This
comes from the ErrorClass, which is a parent class for many ABC classes,
including the toolbar. This comes from the file declaration for logging errors as the
DRIVER attribute is part of the declaration.
To remedy this, simply place the ASCII driver in your project tree. You will now
get a clean compile.
RelationTree.ExpandAll PROCEDURE
CODE
FREE(SELF.QRT)
FREE(SELF.LDQ)
SELF.LoadAll = True
Of all the methods, these two are really incomplete as far as functionality goes.
The final line of code should have a call to load a given file. The reason this code is
deliberately missing is because the templates will know which file to load. Also,
the plan is for the templates to place a call to the PARENT object, which executes
the above code first. The other design plan is that the templates will extend this
class by adding new methods to the derived declaration.
Those are all the methods you need for the base class. Now it’s time to create the
template thatwill provide the remaining functionality.
143
Programmi ng Objects In Clari on
144
Chapter 6
WRITING TEMPLATE WRAPPERS
This is a book about OOP, so what’s a chapter on template writing doing here? The
goal in this chapter is to write a template wrapper for the CLASS created in the
previous chapter. What this means is that the TreeClass is fine for what it does,
but a template can certainly make implementing and using the class easier in
applications. For those not familiar with templates, I recommend you read the
Clarion help on the template language. You may also want to read some of the
template articles at Clarion Magazine
(http://www.clarionmag.com/cmag/topics.html).
The target audience of this chapter is all template programmers, regardless of skill
level.
The purpose is to describe how to implement ABC compliant templates that
generate global, or procedure local objects (or new class types). You may wish to
revisit this chapter many times as your template writing skills improve. It provides
some insight into how to author ABC compliant templates, and why. It also
contains some tips and tricks for getting access to symbol values which are
otherwise unavailable.
There are not many template coders in the Clarion world. You could blame the
structure of the templates themselves for this. Unlike the Clarion language, which
provides many statements that encompass a lot of functionality (like ACCEPT, OPEN
etc.), the template language requires you to be quite specific and detailed in
programming many tasks. In this regard, the template language seems primitive in
comparison to the Clarion language.
145
Programmi ng Objects In Clari on
On the other hand, there are some template statements that do a lot in one line of
code. Therefore, these statements can also viewed as advanced, in comparison to
the Clarion language.
Templates also have a few sets of commands that could be best described as
synonyms. There are differences among these sets of commands, but the
differences are subtle. This gives rise to confusion as one starts to wonder which
command is the most appropriate. That is just one point on the learning curve.
The template language is similar to playing chess. It is easy to learn, difficult to
master. The rules are simple, but one must understand the subtleties to really
appreciate them. Studying the template language is not a waste of time, even if the
payoff comes later.
Templates are viewed by some to be superior to the Clarion language in that they
can generate more than just the Clarion language. In that respect, they are correct.
There are developers who use templates to generate ASP (as SoftVelocity has
done), COBOL, C++, and Perl. PHP and JSP are others that you may see soon.
They can even generate client side Java applications
(www.softmasters.com.ar/eng/jaguar.html) and .NET languages such as C#
(http://net.radventure.nl/Fenix/).
My biggest complaint with templates is the lack of tools to assist the template
developer. There is no template debugger or dialog formatter, so for the most part
templates are hand coded. The template writer tool from SoftVelocity is a good
step in the right direction, but it is limited. It is, however, an excellent tool for
getting started if you wish to start learning the language.
The Clarion Handy Tools (www.cwhandy.com) by Gus Creces has tools to assist the
template coder. Plus there is template code in ABC that is useful for debugging
class wrappers. So there are tools out there, but a template debugger is sorely
lacking. Consider this my open letter to the community to author one. I’ll be your
first customer.
146
Writing Template Wrappers
Once you implement an object using the procedures described in this chapter,
you will be able to:
• Change the object name (for objects declared inside procedures
only).
• Change the base class of any object.
• Embed code in any public/protected method of any object.
• Add new methods and properties to any object.
Class reader
For an ABC class type to be available to the user and Application Generator, the
Class Reader must have read it. In other words, the header file (normally.INC)
containing the class declaration must have been processed. This will happen
automatically, when you launch Clarion, if the header file exists in the libsrc
directory and it contains the text !ABCIncludeFile as the first text in the file.
The header reader will ignore any file that does not contain this comment, so if
the comment is missing, that file’s classes will not be available. This usually results
in generation time error messages and badly generated code. Header file reading is
triggered by a call to %ReadABCFiles, which I will cover later.
Since automatic header reading happens only at the start of your session, you may
need to force a reread or refresh, for example, if you change the header files while
the environment is active. If so, you may press the Refresh Application Builder
Class Information button available anywhere %ClassPrompts or
%GlobalClassPrompts is inserted, or on the global classes tab.
When I start a new template, I usually add it to a base or root template, which is a
template file with a TPL extension. I don’t put each new template in its own TPL
file because TPLs have to be registered. Instead, I simply put the new template in a
TPW file and use the #INCLUDE statement to load the TPW file.
147
Programmi ng Objects In Clari on
The template registry function in Clarion’s IDE looks only for TPL type files and
by default, in the %ROOT%\template folder, although these files may be anywhere
on your system. You just need to walk the folder tree to find them every time you
register a template or add another entry in the RED file for other template folders.
The reason for a base template is simple. I can add whatever templates I wish to it.
When SoftVelocity issues a new release of Clarion, then I am not forced to edit
their templates.
I always state a new template with the #TEMPLATE statement like the following:
#TEMPLATE(CBook,'Clarion Book Template'),FAMILY('ABC')
NOTE There is no color syntax for templates in the Clarion editor. This
makes template code difficult to read. In the extras folder in the
source code zip, I’ve included some C55EDT.INI settings you may
use to add color syntax support for your environment.
148
Writing Template Wrappers
#SYSTEM
#TAB('Clarion Book')
#BOXED('About this template'),AT(5)
#DISPLAY('')
#DISPLAY('This is the template from the Clarion Book.’)
#DISPLAY(‘ The primary goal of this template is')
#DISPLAY(' simply to show how to code class wrappers.')
#DISPLAY('')
#DISPLAY('Suggested use for templates like this one’)
#DISPLAY(‘ is so that one can "hang" other templates.')
#DISPLAY('This ensures that future releases ')
#DISPLAY('of other templates do not step on any custom’)
#DISPLAY(‘templates you may use.')
#DISPLAY('')
#DISPLAY('This template is the wrapper for the Relation’)
#DISPLAY(‘Tree object. The Clarion and ABC templates does ')
#DISPLAY('not use classes for these controls.')
#DISPLAY('Thus, this template provides the same’)
#DISPLAY(‘ functionality as those templates, but')
#DISPLAY('from a class.')
#DISPLAY('')
#ENDBOXED
#PROMPT('Default column number for template generated ↵
comments: ',SPIN(@N2,50,99)),%ColumnPos,DEFAULT(75),↵
AT(205,,25)
#ENDTAB
149
Programmi ng Objects In Clari on
Figure 6-1:
150
Writing Template Wrappers
#CONTROL Template
A better choice for the RelationTree wrapper is a #CONTROL template. The code
looks like this, but in the source it is on one line. I’ve wrapped some of the lines
for better readability here:
#CONTROL(RelationalTree,'Relational Tree Object List↵
Box'),PRIMARY('Relational Tree Object List↵
Box',OPTKEY),DESCRIPTION('Tree structure related to ' &↵
%Primary),MULTI,WINDOW,WRAP(List)
The name of the template and its description is obvious. The PRIMARY attribute
means a primary file for the set of controls must be placed in the procedure's Table
Schematic. If you use this attribute, the description is required. The OPTKEY
parameter means the key label is not required.
DESCRIPTION specifies the display description of a #CONTROL that may be used
multiple times in a given application or procedure. In this case, you’ll see which
file this tree list is for.
MULTI specifies the #CONTROL may be used multiple times in a given window.
WINDOW tells the Application Generator to make the #CONTROL available in the
Window Formatter.
WRAP specifies the #CONTROL template is offered as an option for the control when
the Translate controls to control templates when populating option is set in
Application Options.
151
Programmi ng Objects In Clari on
When registered, the template appears as follows (your registry will vary
depending on the templates you have registered):
Figure 6-2:
The above example shows the optional family parameter. It is optional in that this
template is an ABC family member. Thus, if left off, the template system expects
this group to be in this template set and will complain if it is not found there. The
following is the #GROUP code:
152
Writing Template Wrappers
#GROUP(%OOPPrompts)
#BOXED(''),AT(0,0),WHERE(%False),HIDE
#INSERT(%OOPHiddenPrompts(ABC))
#ENDBOXED
#!
#GROUP(%OOPHiddenPrompts)
#PROMPT('',@S64),%ClassItem,MULTI(''),UNIQUE
#BUTTON(''),FROM(%ClassItem,'')
#PROMPT('',@S64),%DefaultBaseClassType
#PROMPT('',@S64),%ActualDefaultBaseClassType
#PROMPT('',@S255),%ClassLines,MULTI('')
#ENDBUTTON
The #INSERT statement places code in a #GROUP where #INSERT executes. Using
#GROUP serves two major purposes:
153
Programmi ng Objects In Clari on
Thus, one allows the developer to change the class, the other allows the developer
to generate working code regardless of whether they changed the class or not.
The following code sets up defaults for the Relation Tree object:
#PREPARE
#CALL(%ReadABCFiles(ABC))
#CALL(%SetClassDefaults(ABC),↵
'RTree' & %ActiveTemplateInstance,↵
'RTree' & %ActiveTemplateInstance,'RelationTree')
#ENDPREPARE
#ATSTART
#CALL(%ReadABCFiles(ABC))
#CALL(%SetClassDefaults(ABC),'RTree' & ↵
%ActiveTemplateInstance, 'RTree' &↵
%ActiveTemplateInstance,'RelationTree')
#ENDAT
The above is a small snippet of what is really in the #ATSTART statement in the
template. The rest of the code is there to define symbols, set up values, etc.
One interesting aspect of the code is this section:
#FOR(%Control),WHERE(%ControlInstance = %ActiveTemplateInstance)
#SET(%TreeControl,%Control)
#SET(%TreeQueue,EXTRACT(%ControlStatement,'FROM',1))
#ENDFOR
The #FOR statement loops through the controls on the window that match the
current instance. This is provided as it is possible to place more than one such
control on a window.
The first #SET statement assigns the value of the second parameter to the first
parameter. This is done so you know the name (or label) of the control. This
makes it easy to use in later template code, plus you do not have to issue the #FOR
loop again.
The second #SET assigns the value of the FROM attribute of the LIST control. This
is done via the EXTRACT statement. EXTRACT “string slices” though a control’s
attribute, looking for a specific match. The FROM attribute names a QUEUE that
holds the data that is seen on a LIST. If you want to see how this works, you can
add an #ASSERT and build a string as follows:
154
Writing Template Wrappers
This produces the following string when you generate source (emphasis added):
(TEST.B1$) Error: ASSERT: TreeQueue: QTree ↵
ControlStatement: LIST,AT(23,17,284,100),↵
USE(?RTree),HVSCROLL,↵
FORMAT('800L*IT@s200@'),FROM(QTree)
155
Programmi ng Objects In Clari on
#PREPARE
#CALL(%ReadABCFiles(ABC))
#CALL(%SetClassDefaults(ABC),'RTree' &↵
%ActiveTemplateInstance,'RTree' &↵
%ActiveTemplateInstance,'RelationTree')
#ENDPREPARE
#ATSTART
#CALL(%ReadABCFiles(ABC))
#CALL(%SetClassDefaults(ABC),'RTree' &↵
%ActiveTemplateInstance,'RTree' &↵
%ActiveTemplateInstance,'RelationTree')
#ENDAT
#INSERT(%OOPPrompts(ABC))
You’ve seen this code before, it is the same as what was discussed previously. And
it’s here for the same reasons. You also need to have the same code for the
prompts, with one minor exception:
#TAB('Global &Objects')
#BUTTON('&Relation Tree Class'),AT(,,170)
#WITH(%ClassItem,'RTree' & %ActiveTemplateInstance)
#INSERT(%GlobalClassPrompts(ABC))
#ENDWITH
#ENDBUTTON
#ENDTAB
156
Writing Template Wrappers
instances locally. And this fits well if you need to ensure you have a RelationTree
class that is exportable.
Figure 6-3:
Of course, there is another tab on the global prompts. This is for the name of the
base class:
157
Programmi ng Objects In Clari on
#TAB('C&lasses')
#PROMPT('&Tree default class:',FROM(%pClassName)),↵
%ClassName,DEFAULT('RelationTree'),REQ
#DISPLAY()
#BOXED(' Usage ')
#DISPLAY()
#DISPLAY('If you have another class you wish to use instead, ')
#DISPLAY('select it from the list or use the default shown.')
#DISPLAY()
#DISPLAY('This extension is for use in a global DLL/LIB ')
#DISPLAY('ONLY. Its sole purpose is to ensure the underlying')
#DISPLAY('class is exported. Do NOT add this global')
#DISPLAY('extension twice! Once added to one application,')
#DISPLAY('there is nothing else you need to do.')
#DISPLAY()
#ENDBOXED
#ENDTAB
Figure 6-4:
For more information on using this template, see the next chapter.
158
Writing Template Wrappers
#AT(%BeforeGenerateApplication)
#CALL(%AddCategory(ABC),'CB')
#CALL(%SetCategoryLocation(ABC),'CB','CB')
#ENDAT
This not only sets things in motion for exporting, it places the correct flags and
sets their values properly under the Defines tab in the project editor.
Note that these should be populated onto a Global Objects tab, and a suitably
named button should surround each inserted prompt set.
The second parameter of the #WITH statement wrapping each
#INSERT(%ClassPrompts) is key to the whole system. It becomes the “tag” used
to get the generator’s model of a specific instance of an object. For this reason, it
must be unique within any given scope. Also, it is possible to have more than one
tree list on a window.
The key to this is the symbol %ActiveTemplateInstance. This is a built-in
symbol. The Clarion documentation defines this as “The instance numbers of all
control templates used in the procedure.” Since this is dependent on another
built-in symbol, %ActiveTemplate, it is unique for each tree control you may
place on a window.
159
Programmi ng Objects In Clari on
For consistency, and to ensure that the generator merges “pages” together
nicely, these should appear on a tab labeled Classes, and buttons should be
used to group logically similar/related class types together.
#TAB('Local &Objects')
#BUTTON('&Relation Tree Class'),AT(,,170)
#WITH(%ClassItem,'RTree' & %ActiveTemplateInstance)
#INSERT(%ClassPrompts(ABC))
#ENDWITH
#ENDBUTTON
#BUTTON('&Library Files'),AT(,,170)
#BOXED('Library Files')
#INSERT(%ABCLibraryPrompts(ABC))
#ENDBOXED
#ENDBUTTON
#ENDTAB
#TAB('C&lasses')
#PROMPT('&Tree default class:',
FROM(%pClassName)),%ClassName,DEFAULT('RelationTree'),REQ
#DISPLAY()
#BOXED(' Usage ')
#DISPLAY()
#DISPLAY('If you have another class you wish to use instead, ')
#DISPLAY('select it from the list or use the default shown.')
#DISPLAY()
#ENDBOXED
#ENDTAB
160
Writing Template Wrappers
Figure 6-5:
Figure 6-6:
Now that the prompts have been set up, they must be given their default values. It
is important to realize that developers are never required to enter any of the class
or global object values to generate an application that works - the defaults must
always be enough.
I have snuck in a third tab, which is the same dialog as the one you find in the
Clarion and ABC template chains for the tree lists.
A List of Objects
The ABC templates maintain a list of objects that are currently in scope from the
developer’s perspective. This is currently used by the CallABCMethod and
SetABCProperty groups. It is therefore important that objects are added to the
161
Programmi ng Objects In Clari on
known object list in the %GatherObjects embed point. Each object tag should be
passed to %ObjectList in turn, for example:
#AT(%GatherObjects)
#CALL(%AddObjectList(ABC),'RTree'& %ActiveTemplateInstance)
#ADD(%ObjectList,%ThisObjectName)
#SET(%ObjectListType,'RelationTree')
#ENDAT
The #CALL statement calls a #GROUP. The #GROUP code is placed where the #CALL is
placed. In this regard, it is similar to #INSERT with the NOINDENT attribute.
162
Writing Template Wrappers
table schematic. This function is ideal for template use as there are internal
symbols that retrieve whatever is placed there.
This means that a method must not only be defined, but code written for each of
these methods. This is done via the Class dialog. For the local instance of the
RelationTree, the dialog appears as follows:
Figure 6-7:
The Derive? check is set, which enables the New Class Methods and New Class
Properties buttons. Press New Class Methods and the following dialog appears:
Figure 6-8:
163
Programmi ng Objects In Clari on
This dialog contains a list of all new methods and their prototypes. If you press
Properties the following dialog displays:
Figure 6-9:
The buttons allow you to declare local variables for this method and/or the
code for the method. Both of these buttons take you to the proper embed
points.
Figure 6-10:
The point of showing you these dialogs is that without templates generating these
points, you are required to fill in these manually. This requires a slight alteration of
the template code shown previously to generate the class definition.
164
Writing Template Wrappers
#AT(%LocalDataClasses),↵
WHERE(%ControlTemplate='RelationalTree(CBook)')
#FIX(%File,%Primary)
#CALL(%AddMethods,'Load' & %File,'()')
#FOR(%Secondary),WHERE(%SecondaryTo = %File ↵
AND %SecondaryType = 'MANY:1')
#FIX(%File,%Secondary)
#CALL(%AddMethods,'Load' & %File,'()')
#ENDFOR
#CALL(%SetClassItem(ABC), 'RTree' & %ActiveTemplateInstance)
#INSERT(%GenerateClass(ABC), 'RTree' &
%ActiveTemplateInstance,↵
'Local instance and definition'),NOINDENT
#ENDAT
The %Primary is the file listed first in the table schematic. For example, a
developer may want to add the files shown in the following figure:
Figure 6-11:
The value of %Primary would be Customers. The next file is related to it. Thus
#FIX sets or assigns the value of %Primary to %File. Next a call to %AddMethods,
which is a local #GROUP (more on that shortly). That simply generates the proper
method declaration.
165
Programmi ng Objects In Clari on
The #FOR loop is filtered by the WHERE clause, and ensures that only related files
are considered. It also makes the same call to %AddMethods, but only for related
files.
The #GROUP code is listed below:
#GROUP(%AddMethods,%pMethodName,%pMethodPrototype),AUTO
#DECLARE(%MethodsPresent)
#DECLARE(%LastNewMethodsInstance)
#SET(%MethodsPresent,0)
#SET(%LastNewMethodsInstance,0)
#FOR(%NewMethods)
#SET(%LastNewMethodsInstance,%NewMethods)
#IF(UPPER(CLIP(%NewMethodName))=UPPER(CLIP(%pMethodName)) ↵
AND UPPER(CLIP(%NewMethodPrototype))=↵
UPPER(CLIP(%pMethodPrototype)))
#SET(%MethodsPresent,1)
#BREAK
#ENDIF
#ENDFOR
#IF(%MethodsPresent=0)
#ADD(%NewMethods,%LastNewMethodsInstance+1)
#SET(%NewMethodName,%pMethodName)
#SET(%NewMethodPrototype,%pMethodPrototype)
#ENDIF
#SET(%DeriveFromBaseClass,%True)
The AUTO attribute opens a new scope for the group. This means that any
#DECLARE statements in the #GROUP would not be available to the #PROCEDURE
being generated. In other words, this is a way to define local (to the #GROUP)
symbols.
The first lines define new, local instance only symbols, followed by code that sets
their initial values.
The #FOR loop executes as long as there are values in %NewMethods. The #IF
guarantees an accurate comparison by testing upper case letters. If there is a
match, then the %MethodsPresent flag is set and since there is no longer a need to
loop, a #BREAK is issued. Otherwise, the #FOR loop ends naturally once all values
are read. Which means the %MethodsPresent flag is not set.
166
Writing Template Wrappers
The next #IF tests to see if the %MethodsPresent is not set. If that is true, then
increment the number of %NewMethods and set their names. The last act is to set
the Derive? check box.
What this all means is that the class dialog box adds all the new methods to the
class definition and these are visible in the dialogs as the previous illustrations
show. It means the developer does not have to do any extra work.
This whole exercise is one towayreplace the
%InstancePrefix:Load:%TreeLevelFile ROUTINE in the ABC and Clarion
template chains.
There is an extra parameter, %Attrs. Its use is for adding the THREAD attribute as
part of a CLASS declaration as shown below:
#INSERT(%GenerateClass, 'ErrorManager', 'Global error manager',↵
%False, 'THREAD')
Keep in mind that THREAD is not appropriate for any CLASS declared with the
TYPE attribute.
167
Programmi ng Objects In Clari on
If you would like to add the THREAD attribute after the 5.5 declarations (where
appropriate), you have to edit the shipping templates or make your own group.
Notice the same WHERE clause. You want the embed tree to appear for the proper
instance. The %GenerateVirtuals from ABC is the key to this. Thus, you get an
embed tree that looks similar to this one:
Figure 6-12:
168
Writing Template Wrappers
Notice how it appears next to any other local objects. This sets up added embed
code for any method for the Relation Tree.
This produces the following embed point in the tree (in all embeds, just one is
illustrated):
Figure 6-13:
169
Programmi ng Objects In Clari on
What this gives you is the ability to add your own code before the parent method
is called or after it is called, depending on how you want the overridden behavior
to work.
You must consistently use the object tag as it is passed to %SetClassItem and to
%FixBaseClassToUse on both #EMBEDs. The %GetEmbedTreeDesc call takes
170
Writing Template Wrappers
parameters to describe how to structure the embed tree for this embed point.
Valid values for the first parameter are currently hard coded into the #GROUP – this
may change in the future!
The WHERE clause on the inner #FOR loop enforces that the method in question is
valid for embedding. For example, it ensures the method is not private. These are
really #GROUP structures, thus they can be built like functions, i.e. they return
values. This is a small subtlety of the template language, but it means that you can
build some very powerful template functions.
If %True is passed as a parameter, the template further enforces that only virtual
methods of the class in question are made available – this is used by the file and
relation managers, for example.
The second last line calls %GenerateNewLocalMethods, which is responsible for
generating embed points for any new methods that the user has added to the
object. The first parameter describes how to structure the embed point in the
embed tree and is the same as the first parameter to %GetEmbedTreeDesc. The
second parameter is %True when this is a global object, or omitted otherwise.
Note that these are procedure local objects, and that there are additional #EMBED
dependencies; namely %ActiveTemplateInstance (a procedure can have
multiple browses) .
These #EMBED dependencies must generally align with the dependencies of the
#WITH statement for this object. Procedure local objects usually have an implied
dependency upon %ActiveTemplateInstance.
For procedure local objects this code should appear inside a #AT that points to the
%LocalProcedures embed point. For global objects, it should appear inside an
#AT that points to the %ProgramProcedures embed point.
The Relation Tree template wrapper does not use global data, thus is not needed.
However, if your templates need to generate a declaration in the global data area,
the following code generates the object declaration (using the Web Guard
template):
#AT(%GlobalData),WHERE(%GuardEnabled)
#INSERT(%GenerateClass(ABC),‘Guard’,‘Application Security
Guard’)
#ENDAT
The second parameter is the tag of the object to generate; the third parameter
simply gets generated as a comment on the class declaration line. Optionally, you
171
Programmi ng Objects In Clari on
may pass %True as a third parameter that will force the object to be generated as a
TYPE (i.e., TYPE is appended to the declaration). Global objects should be
generated at the %GlobalData embed point, local objects should be generated at
the %LocalDataClasses embed point.
The following WebBuilder example generates a number of procedure local
objects: note that the objects based upon %Control are generated as class types.
#AT(%LocalDataClasses),WHERE(%ProcedureHasWebWindow())
#INSERT(%GenerateClass(ABC),‘WebWindowManager’)
#IF(%IsFrame())
#INSERT(%GenerateClass(ABC),‘WebFrameManager’)
#ENDIF
#IF(%HasQBE())
#INSERT(%GenerateClass(ABC),‘QBEWebWindowManager’)
#ENDIF
#FOR(%Control),WHERE(%Control <> ‘’)
#CALL(%SetClassItem(ABC),%Control)
#IF(ITEMS(%ClassLines))
#INSERT(%GenerateClass(ABC),↵
%Control,‘Web Control Manager for ‘ & %Control,%True)
#ENDIF
#ENDFOR
#ENDAT
Scoping Issues
As you have seen, %ClassItem is a symbol defined whenever objects are generated
by the templates. All template code is executed in the context of its own local
%ClassItem. This means that from any given template section (#EXTENSION,
#CONTROL, etc.) you can only “see” your own %ClassItem.
Since you cannot see the %ClassItem of anything else, you cannot #FIX it using a
tag that you did not create locally – this is why global objects have their names
fixed. You would never be able to fix %ClassItem of the Application (or global
extension) to the correct tag, and therefore you would always get generation errors
reporting; ‘%SetClassItem: Instance not found!’
The most usual reason to attempt to #FIX an external %ClassItem is to get at the
object name. Declaring a local variable inside the parent component and copying
the object name into it can achieve this. For example:
172
Writing Template Wrappers
#DECLARE(%ManagerName)
#FIX(%ClassItem,’Default’)
#SET(%ManagerName,%ThisObjectName)
Providing that this is done early enough, usually in #ATSTART, and that the child
template has the REQ attribute (requiring the parent template), the child can see
the declared variables of its parent, and hence it can get at the parent object name.
Now that the wrapper portion is done, it’s time to turn your attention to the rest of
the control template. Since the class is for a relation tree, this is a control on a
window, thus the name of the template.
What follows next is the actual Clarion code placed on the window structure. The
template does this by the CONTROLS statement.
#! Add target language code for a generic list box
#! with tree, color and icon attributes
CONTROLS
LIST,AT(,,150,100),USE(?RTree),FORMAT('800L*IT@s200@'),|
FROM(QTree),#REQ
END
#! end controls
The #REQ attribute in this section means that if you delete the control from the
window, then all template code goes with it. If you’ve added embedded code in
this control’s embed points, they are not deleted, but moved to an orphaned
section of the embed tree. You see this section only when orphaned embeds exist.
173
Programmi ng Objects In Clari on
You can find the %RelationTreeProperties group with the rest of the #GROUP
definitions in the file CBGroups.tpw. You can tell this #GROUP belongs to this
template as it is not using the family attribute that you’ve seen so far. This code is
“stolen” from ABRELTREE.TPW with a minor modification. Each icon entry also
has a lookup button. Both ABC and Clarion template chains are strictly entry
controls, making the template difficult to use.
Formula Editor
Not many programmers use the formula editor anymore, but there is template
support for it, so I’ve included that support in this template as well, via the
#CLASS statement. Thus the following line (wrapped for readability):
The text is changed slightly so you can differentiate between the ABC tree if you
place both relation trees on a window. It is theoretically possible to have both trees
on the same window (but the control for both must be placed on the window
first).
Figure 6-14:
174
Writing Template Wrappers
developer adds nothing from the default control? What additional features does
the template support, such as list colors?
While the dialog (what colors, icons, etc.) exposed to the developer really does
not need to change from the ABC version, the ABRELTREE dialog template code
could easily be copied and pasted to a #GROUP. It is rather lengthy, so I tend to put
such dialogs in #GROUPs. I like my template code segmented into functions, thus
any maintenance I do is easier. If you run a test on a dummy application (which is
smart during the coding and testing phase), you can see that the dialogs are
exactly the same. This is a good thing, as anyone used to coding ABC relational
tree lists won’t have anything new to learn.
175
Programmi ng Objects In Clari on
#AT(%CustomGlobalDeclarations)
#INSERT(%FileControlSetFlags(ABC))
#FOR(%Control),WHERE(%ControlInstance =
%ActiveTemplateInstance)
#IF(%ControlHasIcon)
#IF(%TreeTitleIcon)
#INSERT(%StandardAddIconToProject(ABC),%TreeTitleIcon)
#ENDIF
#IF(%PrimaryDefaultIcon)
#INSERT(%StandardAddIconToProject(ABC),%PrimaryDefaultIcon)
#ENDIF
#FOR(%PrimaryConditionalIcons)
#INSERT(%StandardAddIconToProject(ABC),↵
%PrimaryConditionalIcon)
#ENDFOR
#FOR(%Secondary),WHERE(%SecondaryType = 'MANY:1')
#IF(%SecondaryDefaultIcon)
#INSERT(%StandardAddIconToProject(ABC),↵
%SecondaryDefaultIcon)
#ENDIF
#FOR(%SecondaryConditionalIcons)
#INSERT(%StandardAddIconToProject(ABC),↵
%SecondaryConditionalIcon)
#ENDFOR
#ENDFOR
#ENDIF
#ENDFOR
#ENDAT
In the above code each control instance matches the template instance (you could
have more than one control on a window). The code then adds any icons to the
project, at the %CustomGlobalDeclarations point. Notice also the testing for file
types, either a primary or a secondary file that has a many-to-one relationship.
This keeps the icons constant for a given level of the tree. Lastly, the template
checks for any conditional icon usage. Note also that each group called has the
ABC parameter, as you are calling an ABC group.
176
Writing Template Wrappers
I like loading my dialogs with dummy test data, so I can see if the code
generated correctly (if at all!). Thus, my project tree for my test application
appears as follows:
Figure 6-15:
These “icons” were simply entered into the dialogs so I have some known values
to test against. I’ve entered similar data for conditions and colors, again for testing
to ensure the correct code generates at the correct time and in the correct
locations.
177
Programmi ng Objects In Clari on
QUEUE instance the relation tree gets its data. You can take care of all of this with
one embed as follows:
#AT(%DataSection),PRIORITY(3500)
#IF(%AcceptToolBarControl)
%[20]DerivedInstance CLASS(%ToolbarRelTreeType)
TakeEvent PROCEDURE(<*LONG VCR>,WindowManager WM),↵
VIRTUAL %[20]NULL END
%NULL
#ENDIF
%[20]TreeQueue QRelTree
%NULL
#ENDAT
The #IF tests to see if toolbar navigation is in use; if so, the template generatse the
Clarion code to declare and instantiate a derived class with an overridden method.
The second Clarion line (after the #ENDIF), declares the local instance of the
TYPEd QUEUE. The [20] means that no matter how short the %TreeQueue
generated value is, generate its name and ensure that the next generated code has
no more than 20 spaces after it (including the characters for the %TreeQueue
value). This is simply a good way to keep labels aligned and counts only for
neatness.
The %NULL is an internal symbol. In this instance it is used to align closing END
statements and add empty lines to the generated source. Declarations bunched up
together are hard to read.
ThisWindow.Init()
Init is a locally derived ABC method. This is where all setup actions are
performed. Since ThisWindow.Init() is executed only once per procedure run,
this is an ideal place to add some embeds to set up the relation tree.
First, ensure the toolbar knows about the relation tree (wrapped for easier
reading):
178
Writing Template Wrappers
#AT(%WindowManagerMethodCodeSection,'Init','(),BYTE'),↵
PRIORITY(8500), WHERE(%AcceptToolBarControl AND↵
%IsFirstInstance)
Toolbar.AddTarget(%InstancePrefix, %TreeControl)
%InstancePrefix.AssignButtons
#ENDAT
Notice the WHERE statement. If there is a toolbar, and this is the first instance, then
call the toolbar’s AddTarget method so the it “knows” about the relation tree
control. Then call the AssignButtons method, which is one of the methods of
the Relation Tree class. The %InstancePrefix is the label of the local derived
class. The default is RTree1.
The tree control should be set in contract mode as this is the expected state and it
makes the window ready for use faster. Thus, you need this template code:
#AT(%WindowManagerMethodCodeSection,'Init','(),BYTE'),↵
PRIORITY(7500)↵
#!Start tree list in contracted mode
%InstancePrefix.ContractAll()
#ENDAT
The next bit of template code takes care of what to do with icons, if any:
#AT(%WindowManagerMethodCodeSection,'Init','(),BYTE'),PRIORITY(8
500)
#FIX(%Control,%TreeControl)
#IF(%ControlHasIcon)
#FOR(%IconList),WHERE(%IconListType <> 'Variable')
#SET(%ValueConstruct,INSTANCE(%IconList))
#IF(%IconListType = 'Built-In')
%TreeControl{PROP:IconList,%ValueConstruct} = %IconList
#ELSIF(%IconListType = 'File')
%TreeControl{PROP:IconList,%ValueConstruct} = '~%IconList'
#ENDIF
#ENDFOR
#ENDIF
%TreeControl{PROP:Selected} = True
#ENDAT
#FIX is similar to #SET. The #FIX statement fixes the current value of the multi-
valued symbol (the first parameter) to the value contained in the second
parameter. This is done so that one instance of the symbol may be referenced
outside a #FOR loop structure, or so you can reference the symbols dependent
upon the multi-valued symbol.
179
Programmi ng Objects In Clari on
The #FOR statement generates the icon list for the tree control only if the icons are
not contained in a variable. In this case, there will be one of two lines generated as
icons may be stored internally or referenced externally.
The last bit generates code to ensure the relation list control is selected, or has
focus.
The next embed in the Init method sets up the alerted keys for the keyboard:
#AT(%WindowManagerMethodCodeSection,'Init','(),BYTE'),PRIORITY(8
750)
#IF(%ExpandKeyCode)
%TreeControl{PROP:Alrt,255} = %ExpandKeyCode
#ELSE
%TreeControl{PROP:Alrt,255} = CtrlRight
#ENDIF
#IF(%ContractKeyCode)
%TreeControl{PROP:Alrt,254} = %ContractKeyCode
#ELSE
%TreeControl{PROP:Alrt,254} = CtrlLeft
#ENDIF
#IF(%UpdatesPresent)
%TreeControl{PROP:Alrt,253} = MouseLeft2
#ENDIF
#ENDAT
All that code does is allow either the default values or the developer-assigned alert
keys to be used. The last bit of code detects if the relation tree is updatable. If it is,
then the code alerts the left button double click as the method to go to edit mode.
The generated code looks similar to this (depending on procedure type, name and
other controls you may place on a window):
180
Writing Template Wrappers
ThisWindow.Init PROCEDURE
ReturnValue BYTE,AUTO
CODE
GlobalErrors.SetProcedureName('Main')
SELF.Request = GlobalRequest
ReturnValue = PARENT.Init()
IF ReturnValue THEN RETURN ReturnValue.
SELF.FirstField = ?RTree
SELF.VCRRequest &= VCRRequest
SELF.Errors &= GlobalErrors
SELF.AddItem(Toolbar)
CLEAR(GlobalRequest)
CLEAR(GlobalResponse)
RTree1.ContractAll
OPEN(Window)
SELF.Opened=True
Toolbar.AddTarget(RTree1, ?RTree)
RTree1.AssignButtons
?RTree{PROP:Selected} = True
?RTree{PROP:Alrt,255} = CtrlRight
?RTree{PROP:Alrt,254} = CtrlLeft
SELF.SetAlerts()
RETURN ReturnValue
I’ve added the emphasis on the lines that this template generated.
ThisWindow.Kill()
There may be times you don’t need code placed in the Kill method. In this
design there is. Since some reference assignments were made, it makes sense to
use the Kill method to clean up after them. Or you may want a local destructor,
it really is your choice. The code for this is simple:
#AT(%WindowManagerMethodCodeSection,'Kill','(),BYTE'),PRIORITY(8
200)
%InstancePrefix.QRT &= NULL
%InstancePrefix.LDQ &= NULL
#ENDAT
This generates code, fairly late in the method, to de-reference the two local QUEUE
structures.
181
Programmi ng Objects In Clari on
If you need to trap for window events, then ensure your template produces the
code for it.
EVENT:GainFocus
The design of the tree list ensures that if the user switches to another window and
then comes back, the code must ensure the tree list is properly updated. This is to
ensure that the other window did not update something.
#AT(%WindowEventHandling,'GainFocus')
%InstancePrefix.CurrentChoice = CHOICE(%TreeControl)
GET(%TreeQueue,%InstancePrefix.CurrentChoice)
%InstancePrefix.NewItemLevel = %InstancePrefix.QRT.Level
%InstancePrefix.NewItemPosition = %InstancePrefix.QRT.Position
%InstancePrefix.RefreshTree()
#ENDAT
List controls generate events. Thus, you need to have template code that generates
proper Clarion code to address this issue.
EVENT:NewSelection
This section of code handles the NewSelection event:
#AT(%ControlEventHandling,%TreeControl,'NewSelection')
#IF(%UpdatesPresent OR %GiveExpandContractOption)
IF KEYCODE() = MouseRight
#IF(%UpdatesPresent AND %GiveExpandContractOption)
EXECUTE(POPUP('%'InsertPopupText|%'ChangePopupText|%'DeletePopup
182
Writing Template Wrappers
↵
Text|-|%'ExpandPopupText|%'ContractPopupText'))
%InstancePrefix.AddEntry
%InstancePrefix.EditEntry
%InstancePrefix.RemoveEntry
%InstancePrefix.ExpandAll
%InstancePrefix.ContractAll
END
#ELSIF(%UpdatesPresent)
EXECUTE(POPUP(||%'ChangePopupText|%'DeletePopupText'))
%InstancePrefix.AddEntry
%InstancePrefix.EditEntry
%InstancePrefix.RemoveEntry
END
#ELSE
EXECUTE(POPUP('%'ExpandPopupText|%'ContractPopupText'))
%InstancePrefix.ExpandAll
%InstancePrefix.ContractAll
END
#ENDIF
END
#ENDIF
#ENDAT
183
Programmi ng Objects In Clari on
#AT(%ControlEventHandling,%TreeControl,'Expanded')
%InstancePrefix.LoadLevel
#ENDAT
#!-------------------------------------------------
#AT(%ControlEventHandling,%TreeControl,'Contracted')
%InstancePrefix.UnloadLevel
#ENDAT
EVENT:AlertKey
The next event is the alert key handling. This template code handles this event:
184
Writing Template Wrappers
#AT(%ControlEventHandling,%TreeControl,'AlertKey')
CASE KEYCODE()
#IF(%ExpandKeyCode)
OF %ExpandKeyCode
#ELSE
OF CtrlRight
#ENDIF
%TreeControl{PROPLIST:MouseDownRow} = CHOICE(%TreeControl)
POST(EVENT:Expanded,%TreeControl)
#IF(%ContractKeyCode)
OF %ContractKeyCode
#ELSE
OF CtrlLeft
#ENDIF
%TreeControl{PROPLIST:MouseDownRow} = CHOICE(%TreeControl)
POST(EVENT:Contracted,%TreeControl)
#IF(%UpdatesPresent)
OF MouseLeft2
%InstancePrefix.EditEntry
#ENDIF
END
#ENDAT
Other Events
The generated code for the last two sections goes in the ABC
ThisWindow.TakeFieldEvent method. Here is a partial listing of what it may
look like:
CASE FIELD()
OF ?RTree
CASE EVENT()
OF Event:AlertKey
CASE KEYCODE()
OF CtrlRight
?RTree{PropList:MouseDownRow} = CHOICE(?RTree)
POST(Event:Expanded,?RTree)
OF CtrlLeft
?RTree{PropList:MouseDownRow} = CHOICE(?RTree)
POST(Event:Contracted,?RTree)
END
END
END
ReturnValue = PARENT.TakeFieldEvent()
185
Programmi ng Objects In Clari on
CASE FIELD()
OF ?RTree
CASE EVENT()
OF EVENT:Expanded
RTree1.LoadLevel
OF EVENT:Contracted
RTree1.UnloadLevel
END
END
The last event handler is the OtherEvent embed. This is an ABC embed where
you may process other events not handled normally, such as user-defined events
or any event in which you may embed a POST message. Think of it as a catch-all
embed for events not otherwise handled by the templates.
186
Writing Template Wrappers
#AT(%ControlOtherEventHandling,%TreeControl)
#SET(%ValueConstruct,%True)
#FOR(%ControlEvent),WHERE(%ControlEvent = 'AlertKey')
#SET(%ValueConstruct,%False)
#ENDFOR
#IF(%ValueConstruct)
CASE EVENT()
OF EVENT:AlertKey
CASE KEYCODE()
#IF(%ExpandKeyCode)
OF %ExpandKeyCode
#ELSE
OF CtrlRight
#ENDIF
%TreeControl{PROPLIST:MouseDownRow} = CHOICE(%TreeControl)
POST(EVENT:Expanded,%TreeControl)
#IF(%ContractKeyCode)
OF %ContractKeyCode
#ELSE
OF CtrlLeft
#ENDIF
%TreeControl{PROPLIST:MouseDownRow} = CHOICE(%TreeControl)
POST(EVENT:Contracted,%TreeControl)
#IF(%UpdatesPresent)
OF MouseLeft2
%InstancePrefix.EditEntry
#ENDIF
END
END
#ENDIF
#ENDAT
The meaning of the above code should be obvious, as it is merely a different twist
on what you’ve read before.
The next task is to generate the methods. There are several ways to do this
depending on the overall task to be accomplished. First, let’s get the toolbar out of
the way:
187
Programmi ng Objects In Clari on
#AT(%LocalProcedures),WHERE(%AcceptToolbarControl)
%DerivedInstance.TakeEvent PROCEDURE(<*LONG VCR>,WindowManager
WM)
CODE
CASE ACCEPTED()
OF Toolbar:Bottom TO Toolbar:Up
SELF.Control{PROPLIST:MouseDownRow} = CHOICE(SELF.Control)
EXECUTE(ACCEPTED()-Toolbar:Bottom+1)
%InstancePrefix.NextParent
%InstancePrefix.PreviousParent
%InstancePrefix.NextLevel
%InstancePrefix.PreviousLevel
%InstancePrefix.NextRecord
%InstancePrefix.PreviousRecord
END
#EMBED(%ReltreeToolbarDispatch,'RelObjTree Toolbar
Dispatch'),%ActiveTemplateInstance,HIDE
ELSE
PARENT.TakeEvent(VCR,%WindowManagerObject)
END
#ENDAT
This template code generates only when there is a need to navigate the tree list by
the toolbar navigation method. Notice that depending on the toolbar button
pressed, the EXECUTE structure calls one of the derived RelationTree methods.
The %LocalProcedures symbol means the local objects for this procedure.
AssignButtons
The method assigns the toolbar buttons to the tree list control. The template code
to do this is as follows:
188
Writing Template Wrappers
#AT(%TreeClassMethodCodeSection,%ActiveTemplateInstance,↵
'AssignButtons','()'),WHERE(%AcceptToolbarControl)
#EMBED(%AssignToolbarButtons,'Assign Toolbar↵
Buttons'),%ActiveTemplateInstance,HIDE
Toolbar.SetTarget(%TreeControl)
#ENDAT
RefreshTree
The template code below generates applicable code to refresh the tree:
#AT(%TreeClassMethodCodeSection,↵
%ActiveTemplateInstance,'RefreshTree','()')
FREE(%TreeQueue)
SELF.Load%Primary()
IF SELF.NewItemLevel
SELF.CurrentChoice = 0
LOOP
SELF.CurrentChoice += 1
GET(%TreeQueue,SELF.CurrentChoice)
IF ErrorCode() THEN BREAK.
IF ABS(SELF.QRT.Level) <> ABS(SELF.NewItemLevel) THEN CYCLE.
IF SELF.QRT.Position <> SELF.NewItemPosition THEN CYCLE.
SELECT(%TreeControl,SELF.CurrentChoice)
BREAK
END
END
#ENDAT
In the class, this is stub method. Almost all of this code could be in the class, but
the primary file is not known to the class and neither is the tree control or the
tree’s data queue. You could add some properties to the class to represent these
items and use those properties. In this case, the template code for the
RefreshTree is not needed. However, you will need to change some of the class
methods. If you feel a little adventurous, consider this an exercise.
ContractAll / ExpandAll
These two template embeds are rather interesting, and demonstrate the concept of
what code goes in a class and what code the templates are responsible for.
189
Programmi ng Objects In Clari on
#AT(%TreeClassMethodCodeSection,↵
%ActiveTemplateInstance,'ContractAll','()')
SELF.Load%Primary()
#ENDAT
#AT(%TreeClassMethodCodeSection,↵
%ActiveTemplateInstance,'ExpandAll','()')
SELF.Load%Primary()
SELF.LoadAll = False
#ENDAT
The point here is that this code generates after the parent call. If you inspect the
method code in the class, it appears as incomplete. And this template code
appears incomplete too. But after generation, the two bodies of code are really
“merged”.
190
Writing Template Wrappers
LoadLevel
This embed produces the code responsible for loading one level of the tree. It
gets called after a user presses the plus (+) button on a tree.
#AT(%TreeClassMethodCodeSection,↵
%ActiveTemplateInstance,'LoadLevel','()')
SELF.CurrentChoice = %TreeControl{PROPLIST:MouseDownRow}
GET(%TreeQueue,SELF.CurrentChoice)
IF ~SELF.QRT.Loaded
SELF.QRT.Level = ABS(SELF.QRT.Level)
PUT(%TreeQueue)
SELF.QRT.Loaded = True
SELF.LDQ.LoadedLevel = ABS(SELF.QRT.Level)
SELF.LDQ.LoadedPosition = SELF.QRT.Position
ADD(SELF.LDQ,SELF.LDQ.LoadedLevel,SELF.LDQ.LoadedPosition)
EXECUTE(ABS(SELF.QRT.Level))
#FOR(%TreeLevelFile)
BEGIN
#IF(%TreeLevel=1)
#IF(%PrimaryKey)
REGET(%PrimaryKey,SELF.QRT.Position)
#ELSE
REGET(%Primary,SELF.QRT.Position)
#ENDIF
#ELSE
REGET(%TreeLevelFile,SELF.QRT.Position)
#ENDIF
SELF.FormatFile(%TreeLevelFile)
END
#ENDFOR
END
PUT(%TreeQueue)
EXECUTE(ABS(SELF.QRT.Level))
#FOR(%TreeLevelFile),WHERE(INSTANCE(%TreeLevelFile) > 1)
SELF.Load%TreeLevelFile()
#ENDFOR
END
END
#ENDAT
The assumption is that the mouse down row is accurate enough to get the current
choice. This is retrieved from the data queue structure. If the Loaded flag is not
set, then an assignment of the current level is passed to the queue structure and
written back. The Loaded flag is set to true. The loaded queue structure gets its
data from the data queue.
191
Programmi ng Objects In Clari on
Based on the level, an EXECUTE structure is entered. For every file in the tree, the
template builds a BEGIN END structure. Inside these structures, there are only two
lines of code generated, one to REGET and the other to format that file’s data.
Inside the BEGIN structure, the template tests to see if this is the first file in the
table schematic. If it is, then another test to see if a key is also in the schematic. If
there is, then a REGET statement is generated based on the key. Otherwise, it is file
based. The remaining files in the table schematic use the file based REGET form.
After the format call, the changed queue data is written back to the queue. Then a
final EXECUTE structure generates to load the current child file.
192
Writing Template Wrappers
UnloadLevel
This embed is almost the same as the LoadLevel embed. The purpose of this
embed is to remove data from the queue structures when the user presses the
minus (-) button on the tree.
#AT(%TreeClassMethodCodeSection,↵
%ActiveTemplateInstance,'UnloadLevel','()')
SELF.CurrentChoice = %TreeControl{PROPLIST:MouseDownRow}
GET(%TreeQueue,SELF.CurrentChoice)
IF SELF.QRT.Loaded
SELF.QRT.Level = -ABS(SELF.QRT.Level)
PUT(%TreeQueue)
SELF.QRT.Loaded = False
SELF.LDQ.LoadedLevel = ABS(SELF.QRT.Level)
SELF.LDQ.LoadedPosition = SELF.QRT.Position
GET(SELF.LDQ,SELF.LDQ.LoadedLevel,SELF.LDQ.LoadedPosition)
IF ~ErrorCode()
DELETE(%LoadedQueue)
END
EXECUTE(ABS(SELF.QRT.Level))
#FOR(%TreeLevelFile)
BEGIN
#IF(%TreeLevel=1)
#IF(%PrimaryKey)
REGET(%PrimaryKey,SELF.QRT.Position)
#ELSE
REGET(%Primary,SELF.QRT.Position)
#ENDIF
#ELSE
REGET(%TreeLevelFile,SELF.QRT.Position)
#ENDIF
SELF.FormatFile(%TreeLevelFile)
END
#ENDFOR
END
PUT(%TreeQueue)
SELF.CurrentLevel = ABS(SELF.QRT.Level)
SELF.CurrentChoice += 1
LOOP
GET(%TreeQueue,SELF.CurrentChoice)
IF ErrorCode() THEN BREAK.
IF ABS(SELF.QRT.Level) <= SELF.CurrentLevel THEN BREAK.
DELETE(%TreeQueue)
END
END
#ENDAT
193
Programmi ng Objects In Clari on
This embed makes the same assumption LoadLevel does, in that the mouse down
row property is accurate enough to detect which line of the tree to affect. The data
queue item is retrieved by that value.
If the Loaded flag is true, the Level field is set to negative. The Loaded flag is
then set to false. The data queue’s fields are then primed and an attempt to get a
record follows. If there is not an error, the current record is deleted.
The EXECUTE structure is then built exactly as LoadLevel. Unlike LoadLevel,
there are two extra lines of code generated after the PUT. The current level is saved
and the current choice is incremented by 1. This keeps the highlight bar at the
same position.
The final act is to generate a LOOP structure. Its purpose is to remove all data items
under the current node.
FormatFile
At first glance, this embed does not appear to do much work.
#AT(%TreeClassMethodCodeSection,↵
%ActiveTemplateInstance,'FormatFile','(*FILE xFile)')
#FOR(%TreeLevelFile),WHERE(INSTANCE(%TreeLevelFile) = 1)
EXECUTE(ABS(SELF.QRT.Level))
BEGIN
#INSERT(%FormatPrimary)
END
#ENDFOR
#FOR(%TreeLevelFile),WHERE(INSTANCE(%TreeLevelFile) > 1)
BEGIN
#INSERT(%FormatSecondary)
END
#ENDFOR
END
#ENDAT
The reason for these two groups is that the template code to do either task is quite
complex and slightly different. Therefore, it makes sense for a template
programmer to use this technique. It does keep certain tasks segmented so that
any maintenance you may need to do is easier.
194
Writing Template Wrappers
These groups are responsible for ensuring the icons, conditional icons, colors, and
conditional colors are properly set. Also, icons could be external file resources or
linked into the current project. Feel free to inspect this code in the file
CBGroups.tpw, located in the template folder.
The above code looks to see if a symbol is declared. If not, it declares it. The
reason for this is that this template code is “re-entrant”, meaning that it gets
executed many times. If this check is not done, you’ll get complaints during
source generation that a symbol is already declared.
#SET(%NewInstance,%NewInstance + 1)
#IF(%PrimaryFlag=0)
#SET(%PrimaryFlag,%PrimaryFlag + 1)
#SELECT(%TreeLevelFile,1)
#IF(%TreeTitle)
SELF.QRT.Display = '%TreeTitle'
SELF.QRT.Loaded = False
SELF.QRT.Position = ''
SELF.QRT.Level = 0
#INSERT(%IconGroup),NOINDENT
#CALL(%ColorGroup)
ADD(%TreeQueue)
#ENDIF
This section of template code increments the local symbols. In the Clarion
language, simple incrementation is easy:
195
Programmi ng Objects In Clari on
NewInstance += 1
However, the templates do not support that type of syntax. The #SET command
must be used. It assigns a value to a user-defined symbol.
Next is a check to see if the %PrimaryFlag is zero, which it should be the first
time this template code executes. If it is, then another #SET command increments
the current value. On the next pass, the template code after the #ELSE executes (a
bit further in this code).
The first file in the schematic is obtained with the #SELECT statement. The
#SELECT statement is really a form of #FIX, in that it fixes a symbol to an instance.
If there is a %TreeTitle (an entry on the tree dialog), then the code sets the
display accordingly. Since a tree title is not based on a file, it is not loaded, nor
does it have any level values.
The next two lines of template code are different, but they are not different in
functionality. #INSERT has an optional attribute, NOINDENT. This means any code
in this #GROUP generates in the same column as the line above it. #CALL is the
same as the #INSERT with the NOINDENT attribute.
Access:%Primary.UseFile()
#IF(%PrimaryKey)
SET(%PrimaryKey)
#ELSE
SET(%Primary)
#ENDIF
LOOP
#EMBED(%BeforePrimaryNext,'Relational Object Tree, ↵
Before NEXT on primary file'),%ActiveTemplateInstance,↵
MAP(%ActiveTemplateInstance,↵
%ActiveTemplateinstanceDescription),LEGACY
IF Access:%Primary.Next() <> Level:Benign
IF Access:%Primary.GetEOF()
BREAK
ELSE
POST(EVENT:CloseWindow)
RETURN
END
END
#EMBED(%AfterPrimaryNext,'Relational Object Tree, ↵
After NEXT on primary file'),↵
196
Writing Template Wrappers
%ActiveTemplateInstance,MAP(%ActiveTemplateInstance,↵
%ActiveTemplateinstanceDescription),LEGACY
The above code uses ABC method calls. If there is a key in the schematic, then the
templates generate code to set read access by key. Otherwise, the access is set by
record order.
A LOOP structure is then generated. Just inside the LOOP is an embed point. This
has the LEGACY attribute on it, meaning that it won’t appear in the embed tree
until the developer presses the legacy button on the embed tree toolbar.
The next section of code tests an ABC call to see if reading the next record returns
anything besides a LEVEL:Benign (no error). If it does, then test for end of file
using an ABC method call. If it has reached the end of file, then break out of this
loop. Any other error is deemed fatal and a close window event is posted to the
current window, in effect closing the procedure.
Next is another legacy embed, which happens after a record is read successfully.
#IF(%PrimaryFilter)
IF ~(%PrimaryFilter) THEN CYCLE.
#ENDIF
SELF.QRT.Loaded = False
#IF(%PrimaryKey)
SELF.QRT.Position = POSITION(%PrimaryKey)
#ELSE
SELF.QRT.Position = POSITION(%Primary)
#ENDIF
SELF.QRT.Level = %TreeLevel
#IF(%TreeChildFile)
#INSERT(%TreeChildGroup),NOINDENT
#SET(%PrimaryFlag,%True)
#ELSE
SELF.FormatFile(%TreeLevelFile)
ADD(%TreeQueue,POINTER(%TreeQueue) + 1)
#ENDIF
This section of code first tests to see if any filter is applied. If so, then code
generates to cycle to the top of the loop if not filtered. The next #IF tests to see if a
key is in effect, and if so, generate the correct code to use the key to determine the
current position. Otherwise, the code uses the file to determine the current
position.
If this is a child file, then the code to handle child files is inserted. Then the
template assigns a true value to the %PrimaryFlag symbol to flag that primary file
197
Programmi ng Objects In Clari on
processing is completed. If this is not a child file, the template calls the
FormatFile method, with the current file passed as a parameter. After returning
from this method call, it adds any queue fields values to the queue structure, in
sorted order.
The following template code executes when the %PrimaryFlag is not zero.
#ELSE
#FOR(%TreeLevelFile),WHERE↵
(INSTANCE(%TreeLevelFile) = %NewInstance)
#FIX(%File,%TreeLevelFile)
#FIX(%Secondary,%TreeLevelFile)
#FIX(%Relation,%TreeParentFile)
#FIX(%Key,%FileKey)
#FOR(%RelationKeyField)
#IF(%RelationKeyField)
%RelationKeyFieldLink = %RelationKeyField
#ELSE
#FIX(%KeyField,%RelationKeyFieldLink)
#IF(%KeyFieldSequence = 'ASCENDING')
CLEAR(%RelationKeyFieldLink)
#ELSE
CLEAR(%RelationKeyFieldLink,1)
#ENDIF
#ENDIF
#ENDFOR
The #FOR loop is an interesting bit of code. There is a WHERE clause attached to it,
which is a template way of filtering. In this case, the filter is if the
%TreeLevelFile instance equals the %NewInstance value. Remember, the
%NewInstance is incremented every time this template code executes. Next a
series of #FIX commands. #FIX is like the Clarion assignment of:
File = TreeLevelFile
Secondary = TreeLevelFile
and so forth. Next is another #FOR loop for each relating key field. If the current
field is part of a relating key, then the template generates as many lines of code as
necessary to prime the relating or matching values. Otherwise, if not a relating
field, it assigns the linking field to the key field. This is for those occasions where
a custom relation is defined in the table schematic.
198
Writing Template Wrappers
The next logic branch determines how to generate the CLEAR statement, the
default low value, or the option second parameter to clear to high values in the
case of descending field sort order.
Access:%File.UseFile()
SET(%FileKey,%FileKey)
LOOP
#EMBED(%BeforeSecondaryNext,'Relational Object Tree,↵
Before NEXT on secondary file'),↵
%ActiveTemplateInstance,%Secondary,↵
MAP(%ActiveTemplateInstance,↵
%ActiveTemplateInstanceDescription),LEGACY
IF Access:%File.Next()
IF Access:%File.GetEOF()
BREAK
ELSE
POST(EVENT:CloseWindow)
RETURN
END
END
#FOR(%RelationKeyField),WHERE(%RelationKeyField)
IF %RelationKeyFieldLink <> %RelationKeyField THEN BREAK.
#ENDFOR
#EMBED(%AfterSecondaryNext,'Relational Object Tree,↵
After NEXT on secondary file'),↵
%ActiveTemplateInstance,%Secondary,↵
MAP(%ActiveTemplateInstance,↵
%ActiveTemplateInstanceDescription),LEGACY
The above template code is pretty much the same as before, but this time, the
code acts on the current secondary file. The following code acts on any child
files to the current child file and again, what it does follows my earlier
explanation, but on the primary file.
199
Programmi ng Objects In Clari on
#IF(%SecondaryFilter)
IF ~(%SecondaryFilter) THEN CYCLE.
#ENDIF
SELF.QRT.Loaded = 0
SELF.QRT.Position = POSITION(%TreeLevelFile)
SELF.QRT.Level = %TreeLevel
#IF(%TreeChildFile)
#INSERT(%TreeChildGroup),NOINDENT
#ELSE
SELF.FormatFile(%TreeLevelFile)
ADD(%TreeQueue,POINTER(%TreeQueue) + 1)
#ENDIF
#ENDFOR
#ENDIF
END
#ENDAT
This embed is quite busy! If not for some calls to #GROUPs, this part of the
template would be quite difficult to read. I’ve omitted the comments here, but if
you open the source files you’ll see that almost every line of code, template and
target language has comments.
You can see that the template makes many decisions about what code to generate,
based on the user settings.
There are three embeds for the methods which handle updates to the relation tree:
• AddEntryServer
• EditEntryServer
• DeleteEntryServer
These three embeds are almost identical, so a discussion of one is enough to cover
the others. The only significant differences are the edit modes.
#AT(%TreeClassMethodCodeSection,%ActiveTemplateInstance,↵
'AddEntryServer','()')
#IF(%UpdatesPresent)
#EMBED(%BeginAddEntryRoutine,'Relational Object Tree,↵
Beginning of Add Record'),%ActiveTemplateInstance,↵
200
Writing Template Wrappers
MAP(%ActiveTemplateInstance,↵
%ActiveTemplateinstanceDescription),NOINDENT
SELF.CurrentChoice = %TreeControl{PROPLIST:MouseDownRow}
GET(%TreeQueue,SELF.CurrentChoice)
CASE ABS(SELF.QRT.Level)
The above template code first checks to see if there was an update procedure
entered in the developer dialog. If not, this template code quits and no target code
generates. If so, then the template declares an embed point. %UpdatesPresent is a
symbol that is set to %True in the #ATSTART section.
Next, the template generates target code to detect the current row and get its level.
The level value is wrapped in a Clarion CASE statement.
If there is a procedure entered in %PrimaryUpdate by the developer, then the
template generates target code to call the update procedure:
#IF(%PrimaryUpdate)
OF 0
#EMBED(%BeforePreparingRecordOnAdd,↵
'Relational Object Tree, Before Preparing for Add'),↵
%ActiveTemplateInstance,%Primary,↵
MAP(%ActiveTemplateInstance,↵
%ActiveTemplateinstanceDescription),LEGACY
Access:%Primary.PrimeRecord
GlobalRequest = InsertRecord
#EMBED(%BeforeCallingUpdateOnAdd,'Relational Object Tree,↵
Before Update on Add'),↵
%ActiveTemplateInstance,%Primary,↵
MAP(%ActiveTemplateInstance,↵
%ActiveTemplateinstanceDescription),LEGACY
%PrimaryUpdate
IF GlobalResponse = RequestCompleted
SELF.NewItemLevel = 1
SELF.NewItemPosition = POSITION(%File)
SELF.RefreshTree()
END
#EMBED(%AfterCallingUpdateOnAdd,↵
'Relational Object Tree, After Update Procedure on Add'),↵
%ActiveTemplateInstance,%Primary,↵
201
Programmi ng Objects In Clari on
MAP(%ActiveTemplateInstance,↵
%ActiveTemplateinstanceDescription),LEGACY
#ENDIF
The above code handles calling the update procedure for the primary file. After a
successful return, it sets the new item level and position, and calls the RefeshTree
method.
The template code now must check for any records in child files:
#FOR(%TreeLevelFile),WHERE(INSTANCE(%TreeLevelFile) > 1)
#SUSPEND
At this point a #SUSPEND statement is encountered. All target code from here to its
matching #RESUME statement, and which is prefixed with #?, is potentially not
generated. The reason is to build, conditionally, any remaining OF structures – the
condition being if there are child files to process. By that I mean any calls to edit
procedures for each child file in the schematic.
#SET(%ValueConstruct,INSTANCE(%TreeLevelFile)-1)
#?OF %ValueConstruct
#IF(INSTANCE(%TreeLevelFile) = ITEMS(%TreeLevelFile))
#SET(%ValueConstruct,%ValueConstruct + 1)
What the above code is doing is simply passing the level number to
%ValueConstruct. This makes the OF statement for each child file.
The following section is conditional, and used only at the bottom of the CASE
statement as it “groups” the last two files. In other words, it REGETs the parent,
then LOOPs through all the matching child records. It then calls the matching
%SecondaryUpdate procedure for that child file.
#?OROF %ValueConstruct
#?LOOP WHILE ABS(SELF.QRT.Level) = %ValueConstruct
#?SELF.CurrentChoice -= 1
#?GET(%TreeQueue,SELF.CurrentChoice)
#?UNTIL ERRORCODE()
#ENDIF
#?REGET(%TreeParentFile,SELF.QRT.Position)
#EMBED(%BeforePreparingRecordOnAdd,↵
'Relational Object Tree, Preparing Record for Add'),↵
%ActiveTemplateInstance,%Primary,↵
202
Writing Template Wrappers
MAP(%ActiveTemplateInstance,↵
%ActiveTemplateinstanceDescription),LEGACY
#?GET(%TreeLevelFile,0)
#FIX(%File,%TreeLevelFile)
#FIX(%Relation,%TreeParentFile)
#?CLEAR(%File)
#FOR(%FileKeyField),WHERE(%FileKeyField AND
%FileKeyFieldLink)
#?%FileKeyField = %FileKeyFieldLink
#ENDFOR
#?Access:%File.PrimeRecord(1)
#?GlobalRequest = InsertRecord
#FIX(%File,%Primary)
#FIX(%Secondary,%TreeLevelFile)
#EMBED(%BeforeCallingUpdateOnAdd,↵
'Relational Object Tree, Before Update on Add'),↵
%ActiveTemplateInstance,%Secondary,↵
MAP(%ActiveTemplateInstance,↵
%ActiveTemplateinstanceDescription),LEGACY
As a final act, it does what the previous update calls did, which is test for
successful completion of the update procedure (e.g. the user did not cancel the
edit).
%SecondaryUpdate
#?IF GlobalResponse = RequestCompleted
#SET(%ValueConstruct,INSTANCE(%TreeLevelFile))
#?SELF.NewItemLevel = %ValueConstruct
#?SELF.NewItemPosition = POSITION(%Secondary)
#?SELF.RefreshTree()
#?END
#EMBED(%AfterCallingUpdateOnAdd,↵
'Relational Object Tree, After Update ↵
Procedure on Add'),↵
%ActiveTemplateInstance,%Secondary,↵
203
Programmi ng Objects In Clari on
MAP(%ActiveTemplateInstance,↵
%ActiveTemplateInstanceDescription),LEGACY
#RESUME
#ENDFOR
END
#ENDIF
#ENDAT
The main difference in the other methods (Edit and Delete), is the passing of the
edit signal to GlobalRequest and issuing of the WATCH statement, plus the
different descriptions of the #EMBED statements.
There are two control templates in addition to the one that places the list control
on a window. These two templates are discussed here.
RelObjTreeUpdateButtons
This template places three buttons on the window. This template also appears
only if the RelationalTree control template exists on the window.
#CONTROL(RelObjTreeUpdateButtons,↵
'Update buttons for a Relational Object Tree'),↵
DESCRIPTION('Update buttons for Relational Tree for '↵
& %Primary),REQ(RelationalTree)
CONTROLS
BUTTON('&Insert'),AT(,,45,14),USE(?Insert)
BUTTON('&Change'),AT(50,0,45,14),USE(?Change)
BUTTON('&Delete'),AT(50,0,45,14),USE(?Delete)
END
The above code declares a control template and the controls to place on the
window. Notice the REQ attribute on the #CONTROL statement. This means that
whatever template is named there, it must be on the window (or report) before
this template appears on any list.
The CONTROLS statement declares that what follows are the controls to place on
the window. This is usually pure Clarion code. Notice the first control is missing
the first two parameters of the AT statement. This means that once the developer
picks this control template, when the mouse moves over the window, the cursor
204
Writing Template Wrappers
changes to a cross-hair shape. When the left button is pressed, the controls are
placed at the spot where the cross-hair is, one after the other.
If you author a control template with many controls, one useful tip is to require
multiple mouse clicks to place all of the controls where you want. To do this,
simply have the fourth or fourth control (for example), omit the first two
parameters of its AT statement.
#ATSTART
#DECLARE(%HelpControl)
#FOR(%Control)
#IF(UPPER(EXTRACT(%ControlStatement,'STD',1))='STD:HELP')
#SET(%HelpControl,%Control)
#BREAK
#ENDIF
#ENDFOR
205
Programmi ng Objects In Clari on
#DECLARE(%AddControl)
#DECLARE(%EditControl)
#DECLARE(%RemoveControl)
#FOR(%Control),WHERE(%ControlInstance =
%ActiveTemplateInstance)
#CASE(%ControlOriginal)
#OF('?Insert')
#OROF('?Add')
#SET(%AddControl,%Control)
#SET(%ValueConstruct,EXTRACT(%ControlStatement,'BUTTON',1))
#IF(%ValueConstruct)
#SET(%ValueConstruct,SUB(%ValueConstruct,2,
LEN(%ValueConstruct)-2))
#ELSE
#SET(%ValueConstruct,SUB(%ControlOriginal,2,
LEN(%ControlOriginal)-1))
#ENDIF
#SET(%InsertPopupText,%ValueConstruct)
The above template code declares symbols for each of the three controls for the
three possible edit methods.
The #FOR loop looks through all controls where the control instance matches the
template instance. This technique ensures that these controls get unique names
when is more than one tree list control on the window.
The #CASE structure looks for the use variables each button might have – in this
instance, possible synonyms for each of the edit actions. Once it finds one with
either use variable labels, it assigns that control to the %AddControl (or one of the
others – see below).
The %ControlStatement contains the control's declaration statement (and all
attributes). EXTRACT is looking for the control type, in this case a button. Since the
first attribute is a BUTTON, the 1 is the third parameter. In plain English, his
statement means “Please give me the button’s text attribute.”
If %ControlStatement finds button attributes (%ValueConstruct has a value if it
does), then the code makes %ValueConstruct contain only the actual attributes.
If %ValueConstruct is empty, then the template use a slightly different method to
assign the attributes.
The final line of this section of code is to assign the value of the construct to the
%InsertPopupText. Thus, whatever the text is, the pop-up menu has the same
text.
206
Writing Template Wrappers
The rest of the #ATSTART section does the same thing for the Edit and Delete
controls, and is omitted here.
When the user presses the button control that does adding or inserting, an
EVENT:Accepted is generated. The CHOICE statement returns the row of whatever
is highlighted. This is passed to the list control, then the code makes a call to the
AddEntry method that belongs to the current object name of the RelationTree.
The change and delete button controls do the same thing, except for the name of
the control and the method call. These method calls are in the parent class, which
set the edit mode and then call UpdateLoop method. Before I get to that method, I
must take a small detour for toolbar navigation.
Toolbar Issues
This template code accounts for toolbar-controlled editing, but does not require a
toolbar. If there is no toolbar, then the corresponding code is bypassed.
#AT(%ReltreeToolbarDispatch,%ActiveTemplateParentInstance),↵
WHERE(%AcceptToolbarControl)
OF Toolbar:Insert TO Toolbar:Delete
SELF.Control{PROPLIST:MouseDownRow} = CHOICE(SELF.Control)
EXECUTE(ACCEPTED()-Toolbar:Insert+1)
%InstancePrefix.AddEntry()
%InstancePrefix.EditEntry()
%InstancePrefix.DeleteEntry()
END
#ENDAT
Notice the #AT location, this is actually a hidden (from the developer) embed.
Embeds with the HIDE attribute are for template use only. They provide a place to
embed code under template control only. You can think of this as the same
functionality as #GROUPs, but without actually defining one.
207
Programmi ng Objects In Clari on
Below is the template code where the above code gets embedded (I think you can
spot where):
#AT(%LocalProcedures),WHERE(%AcceptToolbarControl)
%DerivedInstance.TakeEvent PROCEDURE(<*LONG VCR>,WindowManager
WM)
CODE
CASE ACCEPTED()
OF Toolbar:Bottom TO Toolbar:Up
SELF.Control{PROPLIST:MouseDownRow} = CHOICE(SELF.Control)
EXECUTE(ACCEPTED() - Toolbar:Bottom + 1)
%InstancePrefix.NextParent()
%InstancePrefix.PreviousParent()
%InstancePrefix.NextLevel()
%InstancePrefix.PreviousLevel()
%InstancePrefix.NextRecord()
%InstancePrefix.PreviousRecord()
END
#EMBED(%ReltreeToolbarDispatch,↵
'Relational Object Tree Toolbar Dispatch'),↵
%ActiveTemplateInstance,HIDE
ELSE
PARENT.TakeEvent(VCR,%WindowManager)
END
#ENDAT
Notice the #AT location. %LocalProcedures is a symbol that contains the class
declaration for toolbar management. The template code for this is as follows:
#AT(%DataSection),PRIORITY(3500)
#IF(%AcceptToolBarControl)
%[20]DerivedInstance CLASS(%ToolbarRelTreeType)
TakeEvent PROCEDURE(<*LONG VCR>,WindowManager WM),DERIVED
%[20]NULL END
%NULL
#ENDIF
%[20]TreeQueue QRelTree
%[20]LoadedQueue LoadedQ
%NULL
#ENDAT
This is the code that declares the local instance of the %ToolbarRelType, an ABC
class. %AcceptToolBarControl is a check box on the dialog for the relation tree.
If the developer checks this box, the condition is true and the code generates.
208
Writing Template Wrappers
Special Conditions
There is a special condition that should be taken into account. What if the
developer adds the HIDE or DISABLE attribute to any of the edit buttons?The
following template code addresses this situation:
#AT(%BeginAddEntryRoutine)
#IF(%Control=%TreeControl)
#IF(%AddControl)
IF %AddControl{PROP:Disable}
RETURN
#IF (~%AcceptToolbarControl)
ELSIF %AddControl{PROP:Visible} = False
RETURN
#ENDIF
END
#ENDIF
#ENDIF
#ENDAT
I am showing only the add routine as the change and delete are nearly identical. If
you go back to the template code for AddEntryServer, you will find an #EMBED
labeled %BeginAddEntryRoutine. This template inserts the code at that spot.
So if the current control is the current tree control, and if there is an %AddControl
present, then generate code to test to see if the control is currently disabled. If it is,
the generated code quits there with the RETURN statement.
209
Programmi ng Objects In Clari on
Next, the template code checks to see if the toolbar control is not present, and if
so, generates code to test if the control button is hidden and again, RETURN right
away as there is nothing else to do.
Again, this code generates into an #EMBED point for the AssignButtons method,
but only when the %AcceptToolBarControl check box is set. The template
checks each control to see if it exists, and if so, generates Clarion code to assign
the control buttons to the property for each.
RelObjTreeExpandContractButtons
This control template adds the ability to expand and collapse the entire tree. It is
not very big.
#CONTROL(RelObjTreeExpandContractButtons,↵
'Expand/Contract buttons for a Relational Object Tree'),↵
DESCRIPTION('Expand/Contract buttons for a Relational↵
Object Tree for ' & %Primary),REQ(RelationalTree)
CONTROLS
BUTTON('&Expand All'),AT(,,45,14),USE(?Expand)
BUTTON('Co&ntract All'),AT(50,0,45,14),USE(?Contract)
END
The above declares the template and its controls. It uses the same concepts
discussed earlier to populate these controls on a window.
210
Writing Template Wrappers
#ATSTART
#DECLARE(%ExpandControl)
#DECLARE(%ContractControl)
#FOR(%Control),WHERE(%ControlInstance =
%ActiveTemplateInstance)
#CASE(%ControlOriginal)
#OF('?Expand')
#SET(%ExpandControl,%Control)
#SET(%ValueConstruct,EXTRACT(%ControlStatement,'BUTTON',1))
#SET(%ExpandPopupText,SUB(%ValueConstruct,2,↵
LEN(%ValueConstruct)-2))
#OF('?Contract')
#SET(%ContractControl,%Control)
#SET(%ValueConstruct,EXTRACT(%ControlStatement,'BUTTON',1))
#SET(%ContractPopupText,SUB(%ValueConstruct,2,↵
LEN(%ValueConstruct)-2))
#ENDCASE
#ENDFOR
#ENDAT
The above code does the event processing when the Expand control is accepted. If
that event happens, it then calls the method to expand the entire tree.
211
Programmi ng Objects In Clari on
#AT(%ControlEventHandling,%ContractControl,'Accepted')
%TreeControl{PropList:MouseDownRow} = CHOICE(%TreeControl)
%InstancePrefix.ContractAll()
#ENDAT
This code does the event processing when the Contract control is accepted. If that
event happens, then call the method to collapse the entire tree.
That takes care of the details of creating the template wrapper. I’ve covered a lot of
ground, and don’t worry if you weren’t able to take it all in on first reading. As I
said and the beginning of this chapter, you may want to come back and reread this
material as your skill level progresses. Getting a class to interact fully with the
Clarion development environment is one of the most challenging template tasks
you’re likely to encounter. But once you’ve mastered the basics, you’ll be able to
duplicate your success easily with other classes.
In the next chapter I’ll explain how to fully use this new Relation Tree template.
212
Chapter 7
TEMPLATE USER GUIDE
Now that the template is done, it’s time to put it to use. This chapter covers each
prompt, and also serves as a tutorial. In this case, the tutorial shows how to fill in
the prompts so the resulting tree list looks and behaves just like the one found in
the Invoice example.
Registration
You cannot use the template until you register the template. Open the registry and
press Register. If the registry button is disabled, you are in multi-user mode. You
must uncheck this box under the Application Setup dialog.
213
Programmi ng Objects In Clari on
Figure 7-1:
When the template is registered, it will appear similar to the following figure (this
will differ depending on which templates you’ve registered):
Figure 7-2:
Press OK to close the template registry. The template is now ready for use.
214
Template User Guide
You may add the relation tree control template to any procedure with a window.
The best use is with either a Browse or Window procedure. For simplicity’s sake,
I’ll use a plain window with no buttons.
Press INS anywhere in an existing application (even if you just created a new one).
Give this new procedure any name you wish. Choose Window for the procedure
type.
Figure 7-3:
Press Select to accept this choice. You are now presented with a procedure dialog.
Press Window. You are now presented with a few choices. Normally, you want an
MDI Child Window, as a procedure like this is called from a Frame procedure. It
does not really matter however as long as it is a Window type procedure.
Resize the window to fill the 800x600 outline so you have some room to work.
From either the Control Template button on the Tools toolbar or from the
populate control template option on the main menu, choose Relational ‚ tree
object list box. Once done the dialog closes and when your mouse cursor is over
the window, it changes to a cross hair.
215
Programmi ng Objects In Clari on
Once you determine where you want to drop the control, press the left mouse
button. The list box formatter appears.
Figure 7-4:
Press OK, making no changes, as you do not use this tool. Resize the control so it
fills a little more than half the window vertically with equal margins on both sides
horizontally. At the moment, you have an empty list box.
Right-click on the list box and choose Actions.
216
Template User Guide
The File Details tab is where you’ll do most of your work. These are the prompts
for the control template.
Figure 7-5:
217
Programmi ng Objects In Clari on
Expand Branch
Enter the keystroke that is used to expand the current node of the tree. You may
optional press the ellipsis button to open the key stroke dialog. The default is Ctrl-
Right.
Contract Branch
Enter the keystroke that is used to contract or collapse the current node of the
tree. You may optionally press the ellipsis button to open the key stroke dialog.
The default is Ctrl-Left.
Example: Leave as-is.
Display String
Enter an expression for the primary file. This uses any valid Clarion expressions to
construct how this file’s data appears on the list box.
Example: Enter CLIP(CUS:FirstName) & ' ' & CLIP(CUS:LastName) |
&' '& FORMAT(CUS:CustNumber,@P(#######)P)
218
Template User Guide
NOTE In Clarion 6 and later, press F10 to show the zoom box. It can
display more text than what is allowed on the template dialog, thus
making it easier to enter long strings or expressions.
Update Procedure
Enter or choose from the drop list the name of the edit procedure. This procedure
is usually a form procedure.
Example: Enter UpdateCustomers.
Record Filter
If the primary file in the tree should be filtered in some way, enter the filter
expression here. Any valid Clarion expression that could be used as a filter is
valid.
Example: Leave this blank as there is no filter in this example.
Secondary files
All files listed as related to the primary file are listed here. Since there is nothing
yet in the table schematic, there is nothing to do. You’ll come back to this later.
219
Programmi ng Objects In Clari on
Colors tab
This is where you will enter any colors for this level of the tree. It looks like this:
Figure 7-6:
Foreground Normal
Enter a color equate or a hex value for the normal foreground color. You can also
press the ellipsis button to open the color dialog box and pick the color you wish.
Example: Enter COLOR:Maroon
Background Normal
Enter a color equate or a hex value for the normal background color. You can also
press the ellipsis button to open the color dialog box and pick the color you wish.
Example: Leave this blank to default to no color (black).
220
Template User Guide
Foreground Selected
Enter a color equate or a hex value for the foreground selected (highlight bar is on
the current row) color. You can also press the ellipsis button to open the color
dialog box and pick the color you wish.
Example: Leave this blank to default to no color.
Background Selected
Enter a color equate or a hex value for the background selected (highlight bar is
on the current row) color. You can also press the ellipsis button to open the color
dialog box and pick the color you wish.
Example: Leave this blank to default to no color.
Figure 7-7:
Condition
Enter a valid Clarion expression for the condition. The condition must be true for
the colors to take effect. An example expression would be CUS:InArrears. Any
value in this field would be true.
Example: Leave blank as there are no conditions for this file.
221
Programmi ng Objects In Clari on
Foreground Normal
Enter a color equate or a hex value for the normal foreground color. You can also
press the ellipsis button to open the color dialog box and pick the color you wish.
Example: Leave this blank to default to no color (black).
Background Normal
Enter a color equate or a hex value for the normal background color. You can also
press the ellipsis button to open the color dialog box and pick the color you wish.
Example: Leave this blank to default to no color (black).
Foreground Selected
Enter a color equate or a hex value for the foreground selected (highlight bar is on
the current row) color. You can also press the ellipsis button to open the color
dialog box and pick the color you wish.
Example: Leave this blank to default to no color.
Background Selected
Enter a color equate or a hex value for the background selected (highlight bar is
on the current row) color. You can also press the ellipsis button to open the color
dialog box and pick the color you wish.
Example: Leave this blank to default to no color.
When you are finished, press OK to close this dialog and save any changes or
Cancel to abort any edits and close this dialog. You may add another condition, or
edit an existing condition, or delete a condition at this point if you wish.
222
Template User Guide
Icons
This dialog allows you specify any icons for the tree list.
Figure 7-8:
Default icon
Enter an icon name or press the ellipsis to look up an icon file.
Example: Enter folder.ico
223
Programmi ng Objects In Clari on
you have more than one condition. Using any of the edit buttons shows this
dialog:
Figure 7-9:
Condition
Enter a valid Clarion expression for the condition. The condition must be true for
the colors to take effect. An example expression would be CUS:InArrears. Any
value in this field other than 0 would evaluate as True.
Example: Leave blank as there are no conditions for this file.
Icon
Enter an icon name or press the ellipsis to look up an icon file.
Example: Leave blank as there are no conditional icons for this level in the tree.
When you are finished, press OK to close this dialog and save any changes or
Cancel to abort any edits and close this dialog. You may add another condition, or
edit an existing condition, or delete a condition at this point if you wish.
224
Template User Guide
Local Objects
This tab shows the details for the local object. Press Relation Tree Class and this
dialog appears:
Figure 7-10:
Object Name
This is the default name for the local instance of this class. You may enter another
name for this object if you wish, or leave it as is.
Example: Leave the default name.
225
Programmi ng Objects In Clari on
Base class
This drop list shows all the ABC classes to choose from. Pick a new class from this
list. This is a drop list only if the previous check box is set. If it is cleared, then this
control is an entry where you enter the name of the parent class.
Example: Not applicable.
Include File
This entry control is enabled only if the Use ABC check box is cleared. Enter the
name of the include file where the class definition can be found.
Example: Not applicable.
Derive?
This check box is enabled automatically when one or more files are placed in the
schematic. The files in the schematic must be related to the primary file. Setting
this check enables the following two buttons.
Example: Leave this setting as you find it. It is checked if you’ve added a file,
unchecked if not (you will add a file later).
226
Template User Guide
Figure 7-11:
You may or may not have any methods listed here. The template automatically
adds these methods based on the files in the table schematic. Since the template
automatically keeps this list, you should not add any new methods of your own or
change the names or parameters.
Press Cancel and confirm you wish to cancel (if prompted).
The rest of the buttons are covered in the Clarion documentation.
Press Cancel again and confirm you wish to cancel (if prompted).
227
Programmi ng Objects In Clari on
Classes
The Classes tab shows the name of the parent class. You may change it to another
ABC compliant class that handles trees if you wish.
Figure 7-12:
228
Template User Guide
If you are at the procedure properties dialog, press the Table button. If you are at
the application tree, Right-Click and choose Tables.
Figure 7-13:
Figure 7-14:
Highlight Customers and press Select. The Customer file is now in the tree. Press
Edit and choose KeyFullName. Press Select. With the Customer file selected,
229
Programmi ng Objects In Clari on
press Insert and select Orders. Press Select. Repeat for Detail and Products.
The table schematic should appears as follows:
Figure 7-15:
Press OK to close the table schematic dialog. This adds all the files needed for the
tree list. Press OK to close the procedure dialog (if still open).
230
Template User Guide
The dialog will change to reflect the files added to the table schematic.
Figure 7-16:
You now see the secondary files listed. But you won’t find Products in this list.
Only files that have a 1:MANY relationship to a child file are listed. The same rule
applies the New Methods dialog. Each dialog for the secondary files is the same as
the primary file. To open those dialogs, highlight a file and press Properties. Fill in
the prompts as follows:
Open the secondary dialog for the Orders file and fill in these prompts as
described below.
Display String
Enter: 'Invoice# ' & FORMAT(ORD:InvoiceNumber,@P######P) |
&', Order# ' & FORMAT(ORD:OrderNumber,@P#######P) & |
', (' & LEFT(FORMAT(ORD:OrderDate,@D1)) & ')'
231
Programmi ng Objects In Clari on
Update Procedure
Enter or select from the drop down UpdateOrders.
Select the Color tab.
Foreground Normal: Enter COLOR:Navy
Press OK to close and save your changes. Open the secondary dialog for Detail.
Display String: Leave this blank as the string required is too big to fit in the
maximum entry control size of 255 characters. You’ll use an embed instead.
Update Procedure: Enter or select from the drop down UpdateDetail.
Press OK to close this dialog and save your changes. Press OK to close any other
remaining dialogs until you are back to the application tree.
232
Template User Guide
The Embeds
Open the embed tree by your favorite method. There are many embeds with the
LEGACY attribute in the templates. This means that they are visible only when the
legacy button is pressed on the embed tree toolbar.
Figure 7-17:
233
Programmi ng Objects In Clari on
Using the locator, find Relational Object Tree, After Setting Display on Secondary
File. Expand this tree.
Figure 7-18:
Figure 7-19:
Press OK to close the dialog when done. This does a lookup into the Product file.
You need to add another embed. Press Insert and choose Source. Enter the source
as follows:
234
Template User Guide
!Format DisplayString
SELF.QRT.Display = CLIP(PRO:Description) & ' ('|
& CLIP(LEFT(FORMAT(DTL:QuantityOrdered,@N5))) & ' @ '|
& CLIP(LEFT(FORMAT(DTL:Price,@N$10.2))) & '), Tax = '|
& CLIP(LEFT(FORMAT(DTL:TaxPaid,@N$10.2))) & ', Discount = '|
& CLIP(LEFT(FORMAT(DTL:Discount,@N$10.2))) & ', ' & |
'Total Cost = '& LEFT(FORMAT(DTL:TotalCost,@N$14.2))
Figure 7-20:
Since this template is a pure control template, it will not, by itself, work in a DLL
setting. This is because this class is local and used only when a specific control is
placed on a window.
If you tried to link in an ABC DLL that contains all the ABC class definitions, you
will get a link error for each method call. This is because there is nothing to set up
the exports for this class.
This is why a global extension exists. This extension belongs where the ABC
classes are defined to your application such as a root or global DLL. It is used only
once as that is all you need.
235
Programmi ng Objects In Clari on
Open the Global properties dialog and press Extensions. Choose the only global
extension in the Class CBook node:
Figure 7-21:
Press Select to choose the extension. This adds the extension template to the DLL
application. Look at the following figure:
Figure 7-22:
This template ensures that the RelationTree class and its methods are available
for export, meaning they are available for later applications to call once this DLL is
inserted into the aapplication.
You’ll need to ensure that your DLL projects are correctly set up. If you follow the
instructions for doing this for an ABC application, that is all you need. This
template is designed to follow ABC’s lead, thus nothing extra is required by the
developer.
236
Template User Guide
Congratulations!
By working through the material in this book you’ve gained some incredibly
valuable skills for Clarion development. You’ve learned the basics of object-
oriented programming, and you’ve discovered the magic of virtual methods,
which is at the heart of Clarion’s ABC class library. And you’ve seen how easy it
can be to convert procedural code into maintainable, usable classes, using the
example of Clarion’s Relation Tree.
You’ve also learned how to write templates that automate the process of using
your own custom ABC-compatible classes with the Application Generator. And
finally you’ve seen how you can use all this technology within the AppGen, again
with the example of the Relation Tree conversion.
237
Programmi ng Objects In Clari on
238
APPENDIX A: GETTING SUPPORT
If you do not have access to the Internet, please contact the publisher at the
following address:
CoveComm Inc.
1036 McMillan Ave
Winnipeg, MB
R3M 0V8
Tel: 204-943-5165
Errata
If you find an error in the text, please report it via the above web page.
239
Programmi ng Objects In Clari on
240
Index
global objects 72
INDEX hidden classes 78
RelationManager
code generation 76
relationship of classes to templates 83
using custom classes 73
WindowManager
Symbols
Init 88, 104, 178
%ROOT% 148 Kill 181
_CBDLLMode_ 115 TakeAccepted 90
_CBLinkMode_ 115 TakeCloseEvent 90
TakeCompleted 90
A TakeEvent 90, 209
ABC 71–106 TakeFieldEvent 90, 92, 185
and OOP 4 TakeNewSelection 89, 90
beta releases 2 TakeRejected 90
BrowseClass TakeSelected 90
AddField 88 TakeWindowEvent 90
derived 83 abstraction 9
Init Application Builder Classes
derived 84 see ABC
SetQueueRecord 87, 88 automatic constructor
BrowseQueue see constructor, automatic
SetViewPosition 89 automatic destructor
class reader 147 see destructor, automatic
compatible classes 113
custom global classes 101–106 B
ErrorClass 95–104
binding
AddErrors 97
early vs late 36
deriving 100
late 36
major functions 96
SetErrors 97
C
SetFatality 97
SubString 99 CLASS 10
Throw 97 DERIVED 63
FieldPairsClass example 10
AssignLeftToRight 88 LINK 48, 53
FileManager MODULE 48
code generation 76 THREAD 167
PrimeAutoInc 75, 81 TYPE 12
global embeds 74 class
241
Programmi ng Objects In Clari on
242
Index
243
Programmi ng Objects In Clari on
244
Index
visible to class 37 S
project
scope 37
creating 124
local 15
PROP
SECTION 57
Active 135, 136, 139
SELF
Alrt 132, 180, 181
definition 18
At 132
example 18
Disable 209
Font 55
T
Format 132
From 51, 130 Team Topspeed 2
HScroll 132 template
IconList 132, 179 #CONTROL
Selected 132, 179, 181 with many components 205
Text 67 class wrapper 146
VCR 132 writing 145–212
Visible 209 embed point
VScroll 132 see template, language, #EMBED
PROPLIST family 152
MouseDownRow 137, 138, 139, 142, 185, 187, function
188, 191, 193, 201, 207, 208, 211, see #GROUP
212 initialization 153
PROTECTED 96, 98, 99, 127 language
vs PRIVATE 116 #ADD 166
when to use 27 #APPLICATION 150
vs #EXTENSION 150
Q #ASSERT 155, 167
#AT 162, 168, 169
QUEUE
#ATSTART 153, 156, 173, 205, 207
disposing 52
#BOXED 149
instantiating in constructor 22
#BUTTON 156
#CALL 156, 162, 167, 168, 169, 170, 195
R vs #INSERT 162, 196
reference variable 50 #CASE 206
REGET 192 #CLASS 174
relation tree #CONTROL 151, 172, 204, 210
ABC vs Legacy 110 #DECLARE 166, 173, 195, 205
edit functions 112 #DISPLAY 149
navigation 111 #EMBED 171, 188, 189, 196, 210
REPLACE 99 dependencies 171
HIDE 208
#ENDTAB 149
245
Programmi ng Objects In Clari on
246
Index
247
Programmi ng Objects In Clari on
248