Report Record Highlighting — Fixed Section Height Version

One way to enhance the reader experience for a report is to subtly highlight alternating record sections with a background color. The only problem with this is that there is no $forecolor or $backcolor property for a section of a report. This means that we must use a background object like a rectangle to provide the colored backdrop effect.

This is fairly easy to do in the case of a section whose height remains constant. We will see as we explore this technique that the variable height case is more difficult with the tools currently at our disposal. We will focus on the Record section in this article, but the same principles can be applied to highlighting any section.

The Problem

It would be soooo easy to just make a rectangle object that fills the entire Record section. After all, it’s just a background object, so it won’t get in front of our fields, right? Well, it can still get in the way of our design work just by being there. With a rectangle taking up the entire background space, there is no way to conveniently do a drag-selection of other objects within the section. Even if we “Lock” the rectangle into position, we can’t drag-select any other objects without beginning our drag action out in the report margins somewhere. There are even more problems when we try to do a drag-select across multiple sections! So we’re better off to make the rectangle an unobtrusive size and position it in some obscure and out-of-the-way corner of our record section and then blow it up to the proper size and position in the report instance.

We know that the rectangle must always be put into the (0,0) position within the section (upper left corner), so these are the values we must supply to the $top and $left properties of the rectangle in the simplest case of a report with only a Record section. (We will handle more complex cases in the "General Case" example published separately.) We can safely set these in the $construct method of the report (or better, the $construct method of the object itself) so that the code only needs to be executed once. (Of course, if it isn’t too inconvenient, we could simply place the rectangle at these coordinates in the first place and just make it small enough to be out of the way.)

Unfortunately, a section does not have a $width or $height property as such that we can just copy for our rectangle at instantiation, so we must be a bit more clever to determine these dimensions for our rectangle. In fact, there is something we can do first to make the task of referring to this rectangle in our code easier:

Naming Background Objects

In Omnis Studio, we can give names to background objects on a report — including our rectangle! I repeat for emphasis: We can only do this for report background object, not those on windows or remote forms. Still, this is an important feature!

We must perform this feat using method code. We don’t have access to that tantalizing name property through the Property Manager directly. Suppose our rectangle object has ident 1006 and we want to name it “recordBackdrop”. Here is the code we must provide to “permanently” change its name property:

Calculate $cclass.$objs.1006.$name as 'recordBackdrop'

Notice that we are changing this property at the Class level and not in an Instance. The simplest way to execute this code is to simply double-click the line in the Method Editor. This will cause a yellow arrow to appear just left of this command line — an indication that the method is now “active” and that this is the “Go” point. Clicking the “Step” button in the Method Editors toolbar will execute just this line of code. We can then use the “Clear method Stack” command in the “Stack” menu of the Method Editor to drop out of method execution mode. In fact, we can now delete this method line since it has done its job and is no longer required.

Our rectangle now has a real name. The next time we open the Method Editor for this report class, we will see that name in the object list on the left side of the Method Editor window (if that is the view we have chosen for our Method Editor window, of course). This implies that we can give this object its own methods — which has some intriguing possibilities!

One of these possibilities is that we may want to give the rectangle a custom $print method to manage its size and position. But there are a few other things about the printing process we must understand to intelligently decide where, when and how best to modify the runtime properties of our rectangle.

The Printing Process

Every object in an Omnis Studio report, including each section and each background object, has a built-in method named “$print”. Think of it as a “report event” similar to the “evBefore” event for a field on a window during data entry. It is this built-in method that actually places the object instance onto the current Virtual Page.

As with most built-in methods, we can create a custom method of the same name and override the $print method. But in order for the “real” $print method to perform its basic function (i.e., placing the object on the Virtual Page), we must include a “Do default” command somewhere within our custom $print. We can learn some useful things from creating custom $print methods for our Record section and for some of the objects contained within it and then stepping through these methods as a report executes. Here is some of what can be learned:

The $print method for the Record section begins execution before the $print methods of any of its component objects. This makes sense because the container must exist before items can be put into it. We can modify any of the properties of any of these objects from within this method, if we wish, and know that we are modifying the object instance that will be included in this instance of the section.

When the “Do default” line is executed, method processing jumps to the $print method of the first item contained in the section, just like a subroutine call. In fact, we can see the $print method on the Method Stack in the “Stack” menu of the Method Editor for the report while we step through this code. The first object so “called” will be a background object if any exist in the section. All background objects are placed in the Virtual Section before any foreground objects. When all the objects within the section have had their instances placed within the Virtual Section, that section is then finally added to the Virtual Page and method execution returns to the method line in the $print method of the section immediately following the “Do default” line.

For any individual object, we can only change property values for the current instance before the “Do default” line of $print is executed. After that point, the instance of that object has been sent to the Virtual Section and those property changes will only affect subsequent object instances.

But some properties of the object instance are fixed when $print for that object is invoked. In fact, some properties for a section instance only exist during its $print (like height and width). So we can only access those through the “Position Vector” parameter of $print.

The Position Vector

All objects in a report have at least one parameter variable in their built-in $print method. This is the “Position Vector” — a row variable containing positioning properties for the object. It expresses the size, shape and location the object instance intends to take when the $print method is actualized. This is always the first parameter of a $print method, so we can access it (and even modify its contents!) if we declare a parameter of Field reference type as the first parameter of our custom $print method. I name mine “pPosition”, but the name doesn’t matter. What does matter is the datatype we specify for the variable.

This subject takes up quite a bit of time in my “Omnis Studio Reporting Techniques” course, so I won’t go into more detail about it here. For now, all we need to know is that two of these properties in pPosition are $height and $width. Only within pPosition does a section have width and height properties we can access — and this only exists while the $print method of the section is running.

This points the way to our solution:

The Solution

So we need to make our rectangle the same height and position as our Record section — and then make it disappear for alternating records. No problem! (My rectangle, by the way, is a nice, pleasing-to-the-eye light aqua color — rgb(204,255,255) — with no border (linestyle = 16).)

First, we need to properly place code to set the $top and $left coordinates of the rectangle to “0”. We can put this code in either the $construct method of the report or (my own preference) in a $construct method of the object. The second is slightly better because we can then move this object to other reports and have part of the setup job already done. (I have one word for you, son...“Encapsulation”...)

Next, we need to decide how and where to set the $height and $width properties of the rectangle. Since we want them to match the same properties for the Record section, and since those only exist in the $print method for that section, the $print method of the Record section is the answer to “where”. The answer to “how” is this:

I created an instance variable of Item reference type named “rectRef” to point to the rectangle object and I gave it an initial value calculation of $cinst.$objs.recordBackdrop. I then put the following command lines in the $print method of teh Record section:

Calculate rectRef.$width as pPosition.$width
Calculate rectRef.$height as pPosition.$height
Do default

We now only have to take care of the alternating record visibility for our rectangle. We can create a simple algorithm based on the current record number using the modulus function (mod()). We can do this one of two ways:

mod($cinst.$reccount,2)
mod(#R,2)

The result of this algorithm for a uniformly increasing value with a step of one (the “count”) alternates between “0” and “1” and begins with a value of “1”. If we want to begin with a value of “0”, we could use one of these algorithms instead:

not(mod($cinst.$reccount,2))
not(mod(#R,2))

We can also do this in one of two places: either in the $print method of the Record section (before the “Do default” line) or in the $print method of the rectangle object itself. Again, I prefer the object’s own method. So I will put this code there:

Calculate $cfield.$visible as mod(#R,2)
Do default

If I had chosen to do this in the $print method of the Record section, I would have to use “rectRef” in place of “$cfield” as the qualifier for the property.

That’s all there is to it for the fixed section height case...

Why Only For Fixed Section Height?

When the section contains an extending object, we have a much more difficult time determining the height of the section. Since the $print method of the section executes before that of the extending object, the $height property of pPosition is fixed at the “design height” of the section. The $print method of any extending field is not invoked until the “Do default” line in the section $print method executes.

In fact, the extending object does not know its height until it is actually placed on the Virtual Section. So pPosition in the $print method for that object is not accurate until after the “Do default” line. And since background objects are placed in the Virtual Section before foreground object, it is way too late to set the height of our backdrop rectangle based on the sum of $top and $height for an object that could potentially change the height of the section instance.

Since the $print methods of the component objects are called as part of the “Do default” command in the $print method of the section, we have no way to intervene and re-adjust the height of our backdrop rectangle for a section that might vary in height. If we change the height (or any other property for that matter) of any object after it has been placed in the Virtual Section, that change will not be made manifest until the next instance of that object.

I have not given up hope for finding an answer to the “Variable Section Height” case, but it will be an interesting journey!

Variations On The Theme

There are a number of variations on this basic technique we might choose to use. here are some suggestions:

Alternating Colors

Instead of making alternating backdrop rectangles invisible, we could alternate the colors used for them. Here is the code for changing the $backcolor from aqua to gray (the $backpattern value used is “1”):

Calculate $cfield.$backcolor as pick(mod(#R,2),rgb(170,170,170),rgb(204,255,255))

Highlighting Only Specific Records

If we only want a Record section highlighted if a certain variable has a specific value, we could put this code in the $print method of the rectangle:

Calculate $cfield.$visible as <variable>=<value>

Highlight Colors With Significance

We can combine the two suggestions above into an algorithm that assigns different background colors to records with different values. It is important to limit this to a few options for sanity and ’60s flashback reasons:

Calculate $cfield.$backcolor as pick(<variable>,rgb(<option0>),rgb(<option1>),rgb(<option2>),rgb(<option3>)...)

Different Color For Subtotal And Totals Sections

Why stop at Record sections. Subtotal and Totals Sections and Headers and even Page Headers and Footers might be easier to distinguish if we set different backdrop colors for them as we can for Record sections. We don’t need the visibility option, but the $top and $left coordinates must be dealt with in an entirely different way once we begin introducing sections beyond the Record section (see the "General Solution" document for details on these variations). We can create a separate rectangle for each section and give each one the desired color/pattern combination by default. The sections $print method can then be used as above to set the height and width property values for the rectangle.

More To Learn

There is a lot more to learn about reports in Omnis Studio. Perhaps my “Omnis Studio Reporting Techniques” course might be of use to you. Check out this and other Omnis Studio courses on my web site.