Report Configuration Using
the $construct Method
By David Swain
Polymath Business Systems
In Omnis 7, if we wanted to dynamically
modify some aspect of a report based on some state of the application
or of the data, we had to modify the Report Format, or objects within
it, from a procedure external to that report - a procedure either
in a Window Format or a Menu Format since those were the only places
procedures could exist. When we needed different report layouts
for the same data set - for example, detail and summary versions
of a report - we would simply create a separate Report Format for
each version of the report. This scheme would work fine until we
decided to update some aspect of that report that required the same
or related modifications to all the variations, at which point we
would have to make those modifications to each Report Format (hoping
that we didn't forget any and that we made the changes consistently
for each one).
Today in Omnis Studio our Report Classes can contain methods just
like Window and Menu Classes from earlier Omnis generations. And
as with all instantiable classes in Omnis Studio, Report Classes
can contain a specialized method named $construct. This
method is executed as part of the report instantiation process,
allowing the report instance to reconfigure itself to fit current
circumstances (among other things). We will look at a couple of
simple ways in which we can use this new capability in this article.
Encapsulation
When a Class of any type contains all the methods needed to control
all aspects of its instances (except, perhaps, the spawning of those
instances), we say that it is encapsulated. Such a Class
controls its own destiny - or, at least, the destinies of its instance
offspring. Methods within an instance of such a Class can communicate
with the outside world through messaging, but messages
can also be sent to that instance - including a message
to perform the $construct method (which is sent implicitly
when the instance is spawned). And the message can contain content
in the form of parameters that help the instance understand
what is expected of it.
We will explore one small but significant portion of encapsulation
in this article: self-configuration or encapsulated
setup processes. In future articles, we will look into other
processes we can perform with $construct.
Construction Parameters
Our primary means of receiving setup information from the method
that spawns a report instance are the parameters of the report's
$construct method. Parameter variables have the same scope
as local variables, so they only exist during the execution of the
$construct method and are not in scope for methods invoked
as subroutines of that method. If we want the value passed to a
parameter of a $construct method to be available throughout
our report instance, we need to transfer that value to an instance
variable. Not all constructor parameters require this - only those
that are to be used within subsequent process of the instance beyond
construction. Said another way: If the passed value is only
needed for setup purposes (or other things which can be completely
handled in $construct), we do not need to define an instance variable
for it as well as a parameter variable.
To pass the value of a parameter to an instance variable, we simply:
Calculate <instancevariable> as <parametervariable>
In those cases where this value is needed both for setup purposes
in $construct and elsewhere within the instance, there
is a trick we can use to eliminate this explicit calculation step.
Here is a quick summary:
As long as the instance variable is used in some meaningful way
(not just mentioned in a comment) in $construct, we can
simply assign a parameter variable from that method as the initial
value of the instance variable. Unlike parameters, which come
into existence (are allocated RAM space and assigned the values
passed to them) when the method to which they belong begins execution,
local and instance variables only come into existence when they
are first used. If we use our instance variable within $construct,
then parameters of that method are in scope and will have a value
when the instance variable comes into being. If the instance variable
is not used within $construct but we set a constructor
parameter as its initial value (which we can only do while $construct
is the current method in the Method Editor, by the way), then the
parameter will not contain a value (because it doesn't
even exist!) and the instance variable would be assigned an empty
value as its default. This applies to instances of any
instantiable class - not just reports.
Setting Report Instance Property Values
One of the most basic items we can modify in the $construct
method of a report is a property value of the Report Instance.
To dynamically set the value of a property of an instance of a report,
we can pass a parameter to $construct and then assign the
property value using the parameter variable. We can do this with
many properties of the report instance, but here are a few to pique
you interest:
$name
$recordspacing
$repeatfactor
$islabel
$labelcount
$labelwidth
margin settings
border and background settings
background picture settings
Changes to properties that encompass the entire instance must be
performed before any records are processed for printing. Otherwise,
the instance has progressed to far in its incubation process to
accept a property change. In this article we are only addressing
reports that use the $mainfile or $mainlist setting
for gathering record images to print (with no data manipulation
in $construct itself), so this is not an issue. But in
future articles where we do more interesting things in $construct,
this is an important rule to understand. Ignorance of the rule does
not keep its effects from occurring...
Page properties ($orientation, for example) can be modified
at any time during the printing of the report, but a changed value
will only apply to the next page to be printed, not the
one that is currently being laid out. This is because the Virtual
Page has already been defined and its properties cannot then be
changed. Page properties for the first page of a report
instance (which subsequent pages will also use until the property
value is changed) can be set in $construct following the
rule in the previous paragraph. Here are some page properties that
work in this way:
$orientation
$copies
$scale
$paper
$paperlength
$paperwidth
We can pass parameters to a report instance using any of the commands
that we would normally use for printing a report. Because of the
type of report we are using in this article (Main File or Main List
report), we will use the Print report command to demonstrate
this. This command gives us the option of passing parameters to
the report's $construct method by enclosing a comma-delimited
list of them within parentheses in the appropriate place within
the command parameters:
Here we see both the final form of the Print report command
with a parameter to be passed and the detail block for that command
showing how the parameter is set up to be passed. The Prepare
for print command sends parameters in the same way, while notational
techniques for launching a report instance pass parameters as usual.
That's all the preliminary information we need, now let's look
at a quick example.
Number of Labels Example
Suppose we have a mailing label report and from somewhere within
the application we want our users to decide how many labels will
be printed per record. We may not do this at every access point
to this report, but we do it from at least one location. Since we
may have some locations that launch this report without passing
a parameter, we need to set an initial value for this parameter
to act as a default value when no parameter is passed.
Let's say that our default value for the number of labels to pring
per record is 1.
Setting up an initial value for a scoped variable is easy! We just
supply an expression in the Init. Val/Calc column of the
variable definition as shown here:
In this case, the expression is simply a numeric value.
To use this parameter to modify the $repeatfactor property
value, we simply perform a calculation in the $construct
method:
Then our label report will repeat the Record section
the number of times the user specified using whatever interface
we supplied for that purpose.
Complicating a Perfectly Good Simple Example
But that's just too simple, so let's add another dimension. Suppose
that we want to number each label in the form "x of y"
(with "x" being the label count within the group for a
record and "y" being the number of labels per record),
but only print this is the user so chooses. We can't do all this
from within the $construct method (at least, not with the
reporting technique we are using in this article), so we now need
an instance variable for the label count so we can use that value
elsewhere within the report. Furthermore, we need a second parameter
to carry the user's choice as to whether to print this value or
not. Let's tackle these items separately.
First, getting the user's choice for number of labels per record
(or the default) available throughout the instance. We need an instance
variable for this purpose, so we define one with the same datatype
as our parameter. Since we still need to set the $repeatfactor
for the instance using this value, we can set the initial value
for the instance variable to be the $construct parameter
(which must be done while $construct is the currently selected
method in the Method Editor). Let me emphasize: I am not
saying that we must do it this way; I am merely demonstrating
that we can do it this way. Our instance variable definition
will then look like this:
And our $construct method can now use the instance variable
rather than the parameter variable like this:
The instance variable must be created in order to
be used and setting it's initial value is part of that process.
Parameter variables, on the other hand, are created when the method
first launches since they must be ready to "catch" a value
or reference sent to the method. So the parameter value is available
for initializing the instance variable when that time comes. We
can test this either by changing the initial (default) value of
the pLabelCount parameter variable or by building a simple
window that allows the user to enter a value into a field (representing
an instance variable of the window perhaps named userLabelCount)
and then creating a pushbutton field (or some other control)
that uses the Print report command as shown eralier in
this article to launch the report. This exercise is left up to the
ingenuity of the reader.
Getting the "x of y" display in each label
is an interesting project as well. We need to display the result
of a calculation of the form:
con(mod(<numberoflabelsprintedsofar>,labelCount)+
labelCount*(mod(<numberoflabelsprintedsofar>,labelCount)=0)
The trick is to determine how to get the number of labels printed
so far. We can't use $cinst.$reccount because that is only
the number of records that have been printed, so it will
have the same value for each label in a set. We need something that
will increment for each label that prints. To do this,
we need to define another instance variable. We will call this one
counter and give it the Long integer datatype
with no initial value (which will then default to 0). We can then
place an invisible field (we don't want to see it on the
printed labels) in our Record section somewhere above the
field that is to display the "x of y" value, give that
field counter as its dataname value, set its calculated
value to kTrue and set its text value to counter+1.
Since this field gets processed each time a label image is placed
on the Virtual Page, the associated variable is incremented for
each label printed! So this is the actual print count rather
than the record count. The resulting report can then look
like this (passing a value of 3):
But we only want to print this additional text on the label if
the user so chooses, so we must give them the option on our launcher
window and then pass in a second parameter. If we expand the calculation
of the optional field on the label to choose between the current
expression or an empty string, we would need this parameter's value
throughout the instance, so would have to create an instance variable
for this too. (Of course, we could simply use the parameter to make
the field invisible in this instance, but we'll look at that possibility
in a moment.) So we would define a parameter for $construct
named pShowLabelCount and an instance variable named showLabelCount,
both of Boolean datatype. We would give the parameter variable
a default value of kFalse for those cases where the report
is printed without these choices given to the user (as well as for
testing purposes) and would change our $construct method to read:
Notice that we calculate the instance variable as the constructor
parameter here because there is nothing else we want ot do with
the parameter value at this point. In this case we would use the
following expression as our text value in the counter
field:
pick(showLabelCount,'',con(mod(counter,labelCount)+
labelCount*(mod(counter,labelCount)=0),' of ',labelCount))
If we choose to simply make the "x of y" field invisible
unless the user chooses to display it, we could leave this expression
the way it was, not define a showLabelCount instance
variable and instead change our $construct method to read:
Arguments can be made for either choice, but this leads into our
next topic...
Next Time
I hope this has been of some benefit to you. In the next issue
of Omnis Tech News we will expand on the use of the $construct
method of a report further and look at how we can modify positions
and other attributes of component objects within a report to give
different faces (as in various levels of detail) to the same data
set.
|