The code in ** is the contents of the extension template. The
idea is quite simple, remember the old window size, when it changes step through
each control and scale it accordingly.
The above code is quite acceptable and worked fine. The problem
was the section ‘code goes on a bit’, to do this well you need special cases for
buttons, list boxes, tab, list-boxes on tabs etc etc. The code is easy to write
and as its’ in a template you only have to write it once but it does mean 30-50
lines of code in every procedure.
Therefore I used a device I have been trying to encourage for
some time, the server function. Currently these all loiter in the _sf module of
your application. Put simply all the code from the LOOP to the END can sit
inside a procedure with just HoldX and HoldY passed in. This instantly wipes ½ K
off of every procedure, reduces compile limit problems and speeds compiles.
The code is now structured as follows :-
!**** Global map
ResizeServer(short,short)
! *** Somewhere in program_sf
ResizeServer PROCEDURE(short HoldX, short HoldY)
I SHORT
CODE
LOOP I = FIRSTFIELD() TO LASTFIELD()
Resize_a_control &c
Code_goes_on_a_bit
END
!** Inside the extension template
HoldX SHORT **
HoldY SHORT **
CODE
HoldX =
TARGET{PROP:Height} **
HoldY = TARGET{PROP:Width} **
ACCEPT
CASE EVENT()
OF EVENT:Sized
ResizeServer(HoldX,HoldY)
**
END
END
The above code was implemented and went for testing. At this
point Roy Hawkes (the other member of the template team) came along with a
series of ideas for what we could do with the resize template if only we had a
queue containing details of every control on the screen. I said he was welcome
to try but if the code he produced broke my compiler then I was going to come up
with some really good ideas for what he could do in his section of the
templates. What we actually discovered was that using the same techniques as
above we could radically change the earlier scheme with almost no changes to the
template (and thus the stability of the system). Here is Roy’s new
code.
!**** In the main program map or data
section
ResizeServer(ResizeGroup,ResizeQ)
ResizeInit(ResizeGroup,ResizeQ)
ResizeGroup
GROUP,TYPE
HoldX SHORT
etc
END
ResizeQ QUEUE,TYPE
FieldEquate SHORT
At1
SHORT
At2 SHORT
etc
END
! *** Somewhere in program_sf
ResizeServer PROCEDURE(ResizeGroup RG, ResizeQ RQ)
I SIGNED
CODE
LOOP I = FIRSTFIELD() TO LASTFIELD()
Resize_a_control &c
Code_goes_on_a_bit
END
ResizeInit PROCEDURE(ResizeGroup RG, ResizeQ RQ)
I SIGNED
CODE
RG.HoldX = TARGET{PROP:Height}
RG.HoldY =
TARGET{PROP:Width}
LOOP I = FIRSTFIELD() TO LASTFIELD()
RQ.FieldEquate = I
RQ.At1 = I{PROP:At,1}
etc
ADD(RQ)
END
!** Inside the extension template
MyResize LIKE(ResizeGroup) **
MyQueue ResizeQ
**
CODE
ResizeInit( MyResize , MyQueue ) **
ACCEPT
CASE EVENT()
OF EVENT:Sized
ResizeServer( MyResize, MyQueue )
**
END
END
There are some interesting points and features here.
- The definition of ResizeGroup and ResizeQ are global. They
mean that the actual procedure contains no information about the contents of
either the Group being used or the Queue. This has the advantage that if this
code gets ‘frozen’ into handcode it will still remain up-to-date as it will
change whenever the definitions do.
- Within the implementation of ResizeInit (and server) the
fields of the group are accessed using the 2.0 field selection syntax. This
allows groups (and queues) to be passed around as one block but individual
fields to be accessed simply and efficiently.
- The code in the form routine is now very simple, it doesn’t
get in the way. Someone debugging it can see that resizing is going on, and
when, but their code is not cluttered with the intricacies of Roy’s latest
resizing heuristic.
I thought the solution was rather good and said so. Then I
dropped the bombshell, "In fact its’ so good it shouldn’t take you more that
half an hour to make it into a class."
Some people register excitement by laughing, others by jumping,
others by making yipping noises and others by clapping. Roy had the self control
to avoid all of these. In fact he managed to look singularly unhappy. "Go on,
all you need to do is change the GROUP into a CLASS and move the prototypes and
then do a global exchange of RG for SELF". I was right too. Here is Roy’s code
in OOPspeak.
!**** In the main program map or data
section
ResizeQ QUEUE,TYPE
FieldEquate SHORT
At1
SHORT
At2 SHORT
etc
END
ResizeGroup CLASS,MODULE(‘Rescode.clw’),TYPE
!
Method declarations (PROCEDUREs within a CLASS) have an implicit first parameter
of type
! ResizeGroup.
Server PROCEDURE(ResizeQ)
Init
PROCEDURE(ResizeQ)
HoldX SHORT
etc
END
! *** In Rescode.clw
MEMBER()
MAP.
ResizeGroup.Server
PROCEDURE(ResizeQ RQ)
I SIGNED
CODE
LOOP I = FIRSTFIELD() TO
LASTFIELD()
Resize_a_control &c
Code_goes_on_a_bit
END
ResizeGroup.Init PROCEDURE(ResizeQ RQ)
I
SIGNED
CODE
SELF.HoldX = TARGET{PROP:Height}
SELF.HoldY =
TARGET{PROP:Width}
LOOP I = FIRSTFIELD() TO LASTFIELD()
RQ.FieldEquate = I
RQ.At1 = I{PROP:At,1}
etc
ADD(RQ)
END
!** Inside the extension template
MyResize ResizeGroup **
MyQueue ResizeQ
**
CODE
Init( MyResize , MyQueue ) **
ACCEPT
CASE EVENT()
OF
EVENT:Sized
Server( MyResize, MyQueue )
**
END
END
You will probably look at the above and say, "what’s the big
deal?". The OOP code is no better than the procedural, it is exactly the same
ideas. And you’re right, OOP is just syntactic sugar on top of good procedural
practice. But then again CW is just syntactic sugar on top of a good assembler
…
There is one new piece in the above, rather than hijack the _sf
module we have put the resize code in its’ own module (signaled in the MODULE
statement). We have also used the MEMBER() syntax to unhook the RESCODE source
from the program, this prevents it having to be generated for each
application.
And finally …. There is one last thing we can do although this
is getting more technical. If you look at MyQueue ResizeQ you may notice
something slightly strange. It is not used by the procedure that owns it
but is used by the methods it calls. This is daft, the procedure (and extension
template) should not need to know about the queue, or even if there is one! So
the trick is to move the queue down into the resize class. To do this we need
the concept of the reference.
In Clarion a variable declaration really consists of two
separate parts, the LABEL and the DATA ALLOCATION. In the following
statement
MyShort SHORT
you are really saying three things
- I want a label called MyShort
- I want two bytes of data allocated
- I want MyShort to refer to those two bytes of data
A reference is simply a way of defining a label without defining
data (a), a reference assignment (&=) is a way of assigning a label to some
data c). We also have a way of creating anonymous data on the fly, it is the NEW
function (b).
With these three concepts we can now move the queue entirely
into the CLASS. I will actually annotate this listing as the final
listing to give you a complete idea of exactly what we are doing
:-
!**** In the main program map or data section
ResizeQ QUEUE,TYPE
FieldEquate SHORT
At1
SHORT
At2 SHORT
etc
END
Resize CLASS,MODULE(‘Rescode.clw’),TYPE
Server
PROCEDURE
!Note we can have simple names, procedural overloading copes with
name clashes
Init PROCEDURE
Kill PROCEDURE
!This says HoldQ is a label
of a ResizeQ (but don’t allocate any data for it)
HoldQ &ResizeQ
HoldX
SHORT
etc
END
! *** In Rescode.clw
MEMBER()
MAP.
Resize.Server PROCEDURE
I
SIGNED
CODE
LOOP I = FIRSTFIELD() TO LASTFIELD()
Resize_a_control &c
Code_goes_on_a_bit
END
Resize.Init PROCEDURE
I SIGNED
CODE
!Allocate an anonymous ResizeQ and then attach it to label
HoldQ
SELF.HoldQ &= NEW ResizeQ
SELF.HoldX = TARGET{PROP:Height}
SELF.HoldY = TARGET{PROP:Width}
LOOP I = FIRSTFIELD() TO LASTFIELD()
! We can use the period syntax to go into
structured references. MyClass.MyReference.MyField
Self.HoldQ.FieldEquate =
I
Self.HoldQ.At1 = I{PROP:At,1}
etc
ADD(Self.HoldQ)
END
Resize.Kill PROCEDURE
CODE
! Need to
de-allocated ResizeQ when the resize class dies
DISPOSE(SELF.HoldQ)
!**
Inside the extension template
MyResize ResizeGroup
**
CODE
MyResize.Init ! Init(MyResize) if prefered **
ACCEPT
CASE EVENT()
OF EVENT:Sized
MyResize.Server **
END
END
MyResize.Kill **
And there we have it. A fully OOP resize class. The procedure
that uses this template extension has a minimum of overhead (code or data) and a
minimum of clutter in the procedure. The resize class has not become a
black box (it is still in code you can debug) it has simply been parceled up so
that everything to do with resizing is in one file.
Now just look at the code savings; without the use of server
functions but with Roy’s clever resizing we would probably having been taking a
2-3K hit on each procedure, this has been reduced to about 100 bytes (4 Lines).
A good maxim is an order of magnitude makes a difference. Here we have an
order of magnitude. The difference is the functionality provided by the
templates can now explode without the complexity of the procedure
or the weight of the procedure increasing significantly. In fact I would
expect a future set of templates to be able to provide existing levels of
functionality whilst radically reducing the size of generated code and improving
the readability (by letting the programmer see what is happening rather
than how it is happening).
However, there is a still some icing on the cake for template
writers and users. You see some of the fields in the CLASS declaration
will acquire the PRIVATE attribute, some PROCEDUREs could too although they
won’t need to in this case. These attributes make clear to the template user
those parts of the system that are up for grabs and those parts which
could change from release to release. You can still get at the private items
simply by placing access functions in the rescode.clw module (CW has
particularly clever privacy rules) but you are safe in the knowledge that code
in your main application cannot be accidentally using things that are going to
change. So the OOPization is not simply an efficiency gain but also helps the
long-term maintainability of the application.
Well, I’ve just about managed a 2,000 word essay on OOP without
using a single buzzword (after the first sentence). This is not because I don’t
think these concepts are important, they are, but I learnt to use OOP long
before it was fashionable to know what OOP was and sometimes I believe that is
the best way.
Remember, scientifically speaking a bumble bee cannot fly;
fortunately the bumble bee doesn’t know that ….
…