[Omnis-Newsletter] Omnis Technical Newsletter
omnis-news-admin@omnis.net
omnis-news-admin@omnis.net
Wed, 13 Jun 2001 16:37:44 +0100
Omnis Tech Newsletter June 13th, 2001
========================================
UNSUBSCRIBE OPTIONS: You have been sent this email because you have directly
signed up for, or expressed an interest in receiving a technical newsletter
when you downloaded an evaluation of Omnis Studio, or registered the Lite
version of Omnis Studio. If, however you feel you have received this email
in error you can unsubscribe as well as change your subscription options at
www.omnis.net/newsletter.
N.B. If you subscribed by checking a box on one of our forms, you will not
have received a password. You will need to submit your email address at
www.omnis.net/newsletter and select the 'email me my password' option on the
next page in order to receive this.
========================================
WELCOME!
Welcome and thank you for subscribing to the Omnis Technical Newsletter.
Published fortnightly, it is intended for Omnis developers of all levels and
experience, for those people evaluating Omnis Studio, or for developers
moving from a similar tool. We think you'll find the content both
interesting and useful for your Omnis development needs and hopefully it
will help you become more productive in Omnis application design.
The first article in this newsletter is the next part of our basic Omnis
tutorial in which Geir shows you how to add database classes to your
application. In the second article David Swain explores the use of report
data grids for displaying a list as part of a single record in a report. You
may find it easier to work through the exercises and examples in this
newsletter by printing it out before you begin.
CONTENTS:
-About the Authors
-Building Your First Omnis Application part 6: Adding table classes and
using our schemas in application windows, by Geir Fjaerli.
-About Omnis Support
-Data Grids in Reports, by David Swain
-Copyright and Unsubscribe details
========================================
About the Authors
The Omnis Technical Newsletter contains high quality content from two
leading and well respected Omnis developers, Geir Fjaerli and David Swain.
Geir Fjaerli is based in Norway and has been an Omnis professional developer
for many years as well as a Regional Sales Manager for Omnis. He is
currently working freelance again developing a range of products (in Omnis
of course) including his Prophet5 sales and customer relationship management
solution, soon to be released for Mac OS X.
David Swain is the founder and president of Polymath Business Systems, for
many years a leading provider of Omnis training. His expertise in Omnis
programming and his ability to make complex concepts understandable are
recognized throughout the worldwide Omnis community.
========================================
Building Your First Omnis Application part 6: Adding table classes and using
our schemas in application windows.
By Geir Fjaerli, Sunshine Data
Email: geir@sunshinedata.net
Web: www.sunshinedata.net
----------------------------------------------------------------------
Welcome to a new chapter of our Basic Omnis tutorial. After building our
first basic application in the first chapter, we started looking at the
details of an Omnis application. They included:
Part 2: The Studio Classes. The classes are the building blocks of an
application, and include windows, reports, menus etc.
Part 3: The Omnis Integrated Development Environment. Here we looked at the
Component store, the Property Manager and the Catalog.
Part 4: The Data Structure. Including basic terminology.
Part 5: Creating schemas and database tables.
As usual, I strongly suggest that you refer to these chapters if you haven't
already, or if you feel uncertain about the details. (For back issues,
please go to www.omnis.net/newsletter and click on the Newsletter Archive
link.)
Last time we defined our data structure and built it into a library file
called Tasks. If you haven't already done so, you should complete that part
first. Later we shall provide the existing library as a download for your
convenience, but make sure you complete all steps anyway. It is important
that there are no holes in the foundation of your Omnis knowledge.
So, having completed part 5 you should now have a library with two schema
classes (sEmployee and sTask) and one Startup_Task. Open Omnis Studio and
the Tasks library. You should also have a Session defined in your SQL
Browser, using the OmnisSQL DAM and a data file which I suggested you store
in the same folder as the library.
Since we have not yet created any logon code in our library, we will have to
use the SQL Browser to log on to the database. To do so, open the SQL
Browser window (from the Tools menu in Studio.) From the Session menu in the
window select Open and then TASK from the submenu. An icon (or list row
depending on your display settings) for the TASK session will appear to tell
us that the session is open.
Now we are ready to start using our database classes to create application
windows and logic. But first we are going to add one more item to the
equation: The Table class. The table class was discussed in part 4, refer to
this if you do not remember. A brief quote to jog your memory: "The Studio
table class holds the methods to access and update the database table. The
table class is linked to the database through a schema. Since the schema
holds the database table definition, by linking to it the table class knows
what the database table looks like. The table class comes with a number of
built-in methods."
So our schema classes which we created last time hold the definition of the
data structure, we need to add table classes to hold the logic. This is
really a one to one relationship, and a simple operation: Assuming you have
opened Studio and the library, now open the Browser (from the View menu in
Studio) and double-click the Tasks library to see its classes. You may want
to close the windows that belong to the SQL Browser first, we don't need
them for now. (TIP: On the Windows menu in Studio you find options for
"Close all" and "Close other" windows, so you can easily clean up your
screen. Note that as mentioned earlier Studio saves a class when you close
the editor, so be sure you do not have any unwanted changes before you do
that.)
Open the Component Strore if it isn't already open. It should display the
classes that Omnis offers. Select the "Class Defaults" group and drag a "New
table" into the Browser. Name it "tEmployee" (as always without the quotes)
and hit Return. Right click tEmployee and select Properties. You will find
that the last property is called "sqlclassname". Click this and then the
little triangle that appears on the right side of the line. This will open a
list of available schemas (and query classes if there are any). Select
sEmployee. This binds the table to the schema.
Now repeat the process for sTask. This time call the new table class tTask
and link it to sTask.
Congratulations! This simple operation has added SQL logic to our
application. I have mentioned earlier that the table classes comes with
basic SQL logic built in, so we don't need to program anything to test it.
In fact, you may skip defining the table classes at all, since Omnis is
smart enough to assume you want to use the built in logic if you don't. But
almost invariably you will want them at a later stage, to add primary key
handling etc, so we might as well define them right away. That way all our
code refers to the correct class name from the start.
We mentioned in part 4 that the built in methods include the following:
$select: To select one or more rows matching our "where"-statement, which is
passed as a parameter.
$fetch: Fetches the rows selected by $select from the database into our
application.
$insert, $update, $delete: Inserts rows into, updates or deletes them from
our database.
To see how they are used, we are going to let the Omnis SQL form wizard
build a basic window for us.
Open Component Store and select the second group: Window Classes. The third
icon in this group is the SQL Form Wizard. Drag this into the Browser and
type in the wEmployee before doing anything else. This will be our employee
details window for adding and updating employee information. This time when
you hit return (or click somewhere outside the name of the class) a new
window opens. This is because rather than adding an empty class as we have
done before, we have started a wizard.
A wizard contains logic to help you set up the class that you just added. In
fact, we can divide the items in the Component Store in three groups: Empty
classes: Adds basic classes with no predefined contents or logic. Templates:
A class that comes with some predefined attributes (color, size etc),
objects (fields, boxes, menu lines) and/or logic. Wizards: A class or
template that triggers functionality to guide you through the setup of the
class in your library.
Omnis lets you add you own templates and wizards to the Component Store.
The SQL Form Wizard opens a window asking you for the style of window to
build. The options are:
* Each column in the database (or rather the schema class) gets its own
field on the window.
* The window uses a list (display grid) to show multiple lines of data.
* An enterable grid is a list view that lets you edit the contents of the
list directly, and lets Omnis update the database with your changes.
* A Parent/Child window adds separate fields for the Parent (Employee)
table, and an enterable grid below them for the child (Task) table. This way
you can show one employee and all their tasks.
This time we want to build an Employee details window, so we select the
first option in the wizard: One field per column.
NOTE: The wizard uses so called radio buttons for this first step, small
round buttons which are grouped and where you can only select one of them at
a time. Try clicking any other radio button in the group and see that only
one gets the "dot" that tells you it is selected.
Again, make sure you selected the first option, and the click the "Next"
button at the bottom of the window.
The next step asks us to select the schema and columns we want to use for
the window. This step uses a so called "tree list", which has branches for
the sub groups of an entry (if any). Note the little + sign in front of the
two lines. Click it and you will see the tree expanding to show the columns
of that schema. (A tree list can have many levels of such subgroups, they
are like branches on a tree, hence the name.)
In front of each entry is a "check box". The check box is a square button
which you can select. Unlike the radio buttons which appears in groups, the
check box is individual and normally you can select more than one. In this
particular case there is some added logic: You can only select one schema,
and if you select one, all its columns are selected too.
Select the sEmployee schema, and leave all its columns selected too, since
we want to include all of them in the window. Click "Next" again.
The final database related step of the wizard is to select the session to
use for our window. Omnis allows you to have multiple session open at the
same time, to the same or to many different databases. In our tutorial we
will stick to one session, the one we defined in the SQL Browser as TASK.
Select that by clicking the checkbox and click "Next" to move on to the
layout part of the wizard.
This step lets you use a predefined "look" for your window. Click them to
see how the wizard displays their look. Select one that you like, the logic
will be the same for all of them. Click "Next".
So far we have only told the wizard what to do, it hasn't actually done it
yet. So you can freely go back and forth between the steps, and cancel the
wizard at any time. Once you are satisfied, hit "Finish" to actually build
the window. Once you click it, Omnis will build the window according to your
choices and open the window class in design mode.
The window is now ready to use. As you may remember from the "Hello World"
example in the first part, Omnis is not compiled, anything we build is ready
to use immediately. To test your window simply press "Ctrl-T" (hold down the
Control key, or Command key on the Mac and type a T), to go back to design
mode press Ctrl-T again.
In runtime mode, you can start adding data to your database. This window is
a modeless one, before you start using it let us define the two basic types
of window behavior:
Modal: In a modal window you have to decide what you want to do before you
make any changes. So you will click an Insert button before you type in the
information, an Update button before you change existing data etc. Then you
type in the new/changed information, and finally you click OK/Return to tell
Omnis to update the database.
Modeless: You can always type into the window, without selecting the
operation (mode) beforehand. Then you click the Insert button to perform the
insert, Update to update, an so on.
So with modal interfaces the Insert button only prepares the window for
insert, and you have to click OK to actually insert the data; with modeless
interfaces you don't have to prepare for anything, and the Insert button
performs the Insert directly. Each technique has its advantages, I shall
leave that discussion out of this tutorial.
So with our modeless window, to add data just type in the information in the
field and click Insert to update the database. NOTE: We haven't created any
logic to add primary key values yet, so you have to keep track of these
manually. We defined it as an Integer, so you should use 1, 2, 3, 4 and so
on as your primary key values. Make sure they are unique, as this is what we
will use to connect a Task to an employee later. If two employees have the
same number, there is no way to tell which of them a task with that number
belongs to.
So today we have created our first working database window. More
importantly, that window contains logic to find, insert, update and delete
data using our data classes. Next time we shall look at that code to see how
it is done.
========================================
About Omnis Support
We provide world-class support programs that give you the technical help and
backup you need to build your Omnis applications, whether you're a complete
beginner or an experienced Omnis developer. To help you get the most from
Omnis, our Support Programs provide one-to-one contact with dedicated
technical staff via phone and email, exclusive access to technical notes and
programming techniques, additional tools and components, and full product
updates.
For further details about Omnis support, contact your local sales
representative, or go to:
http://www.omnis.net/support/index.html
========================================
Data Grids in Reports
By David Swain, Polymath Business Systems Inc
Email: dataguru@polymath-bus-sys.com
Web: www.polymath-bus-sys.com
----------------------------------------------------------------------
In the last issue we examined the Headed List Box window class component.
Although we couldn't cover every feature, we did get a glimpse of the great
versatility of this list display object for window classes.
The report class analog for the Headed List Box window field type is the new
(version 3.0) Data Grid external component. It is one of two long-awaited
tools for including a list variable value directly into a field on a report
without having to parse the list into a formatted string value or perform
other feats of programmer legerdemain.
The Data Grid component for reports is so feature-rich that we can't
possibly explain all its possibilities in a single article. Rather than
quickly gloss over all of its properties, we will focus on those most likely
to be needed for basic functionality. We will then provide an exercise that
creates a report that can be used to directly print the contents of any
Headed List Box (well, almost any) in your applications.
A word of caution: The name "data grid" can be a bit misleading as this
report field type has much more in common with a Headed List Box window
field than a Data Grid window field. For this reason, the phrase "report
Data Grid" will be used in this article to refer to this component type.
Differences Between Headed List Box and Report Data Grid Components
The most prominent difference between a Headed List Box field and a report
Data Grid component is that the latter offers us direct control over
columnar properties at design time. For any column we can specify the
heading, content calculation, width, fontstyle, alignment, text color, and
whether the column will autosize to accommodate larger values than the
specified column width allows. We can also modify these individually using
notation at runtime. Neither the displayed content on a line nor the
headings are treated as a simple string with column delimiters.
Of course, since the report Data Grid is printed on a report, there are no
scroll bars to contend with. Vertical scrolling in a Headed List Box
translates to a longer report. Horizontal scrolling may mean the report must
generate additional pages horizontally. (More on this later.)
We also cannot allow the user to resize the columns on a report Data Grid as
we can for a headed List Box. There are two features we can use to
accommodate column values wider than our original width setting for a column
on a report Data Grid.
The first is that we can make a report Data Grid column "autosizing". Such a
column uses the column width setting as a minimum column size, but it will
automatically widen to accommodate the widest value for that column in the
list. (A clever bit of programming if you think about it!)
The second feature is that both column headers and column content can be set
up to wrap onto multiple lines in a report Data Grid component. This can be
used to keep a report from having to use multiple horizontal pages while
preserving the entire content of the list in a readable format.
The report Data Grid also allows us to have a different font size (as well
as style and color) for the header vs. the content of the list. We are also
given fine control over the gaps between columns, between rows, between the
header and the body, between header, body and border, and even between
column content and right column edge for fill justified columns.
If there is anything one could call "missing" from the report Data Grid
component, it is that fieldstyles are not supported. This means that we must
specify the $rowfont property value from the Report Font List of our library
and hope that the font we select matches its counterpart on the other
platforms. Perhaps this will be addressed in a future release.
There are a great number of appearance options for aiding both the
legibility and scalability of the finished report. In fact, the report Data
Grid component is so powerful, it comes close to being a list-based,
columnar sub-report field -- and there are techniques for using it in just
that way. But let's examine how we set one up before we go too far down the
path of how to use one.
General Properties
When using the Property Manager to set measurement values, the report Data
Grid component recognizes the setting of $prefs.$usecms and appropriately
deals in cms or inches as required by that preferences setting. We will see
later that some notational settings deal with a third measurement scale.
The report Data Grid component has the basic properties of any report field
(name, ident, dataname, etc.). The top and left coordinate properties are
very important, but the height and width properties are just a design time
convenience since this field automatically extends in both directions as
required by the list contents. Due to being an external component, the
report Data Grid is automatically "floating" and will not snap into position
with the automatic vertical report grid, so extra care must be taken to
position it vertically if it must be aligned with other objects.
We can limit the report Data Grid to only displaying the selected lines in
the underlying list by setting its selectedonly property to kTrue. There is
also an appearance property (useoddforselected) that allows us to highlight
the selected lines without removing the non-selected ones.
We can have our header text word wrap within the column boundaries as needed
by setting the multilineheaders property to kTrue. Or we could choose to not
have headers at all by setting the ::showheader property (the colons are
necessary) to kFalse. We can have the header repeated at the top of each
page if the grid contents will not fit all on a single page by setting the
ignorevpagebreaks property to kFalse. The related property,
ignorehpagebreaks, makes certain that column contents are not split if
multiple horizontal page printing is required by the width of the grid.
The grid can be made to support styled text (use of the style() function) in
both the header and the grid body by setting the ::styledtext property to
kTrue.
Text Properties
The rowfont property determines which font will be used for the report Data
Grid component. The value for this property is selected from the Report
Fonts Table for the library, so care must be taken that the chosen font
number has an appropriate corresponding font for all supported platforms.
The sizes for this font for the header and the body of the grid are set by
the headfontsize and rowfontsize properties respectively. They do not have
to be the same. The style and color of the text in the header are set by the
headfontstyle and headtextcolor properties respectively. The default style,
alignment and text color of the grid body are set by the rowfontstyle,
rowalign and rowtextcolor properties.
Additional leading for all rows is set using the rowfontextra property. The
value for this property is expressed in points rather than in cms or inches.
It differs from the rowgap property in that it applies to the text itself
rather than to the row. That is, if the column is a multiline column and the
text wraps onto multiple lines, rowfontextra will apply to each line of text
while rowgap is only the space between rows.
As we will see a bit later, nearly all of these properties can be overridden
at the column level.
Appearance Properties
I mention these separately mainly to state that there are an impressive
number of them. Without getting lost in the detail or the possibilities,
let's list these briefly:
We can apply a 3D border to the report Data Grid using any of the border
types for Omnis Studio 3D rectangle objects. (Setting the bordergap property
appropriately will keep the body of the list from butting up to the border
edge.) But beyond that, we have additional control over various of the
constituent parts of certain border types. For shadow borders, we can
independently control the right and bottom offsets of the shadow as well as
its color. For lined borders, we can control the style and color of the
line. For beveled borders, we can independently control the size (in pixels)
of the inner, outer and center parts of the bevel.
We have independent control over the style of the grid lines (including
"None") that separate row, columns and the first row from the header, but
not independent control over the colors of these items. The color of the
grid is set for the entire grid, but this is independent of the color for a
lined border. (We do not have independent control over the line style for
individual column dividers as we do for Complex Grid fields.)
We can set an overall fill color for the grid, but beyond this we can
independently set the forecolor, backcolor and backpattern for the even, odd
and header lines within the grid.
Suffice it to say that we have enough creative control for most presentation
situations. While we can't quite present the list of contestants in the
Scottish Games here in New England emblazoned on their respective clan
tartans, we could easily show rosters for school athletic teams on a
background of their school colors, for example.
Column Properties
The report Data Grid component has an additional tab in the Property Manager
named "Columns". The first two properties under this tab determine how many
columns the grid has at design time (columncount -- which can be overridden
at runtime as will be explained later in this article) and which columns
properties are available in the other property slots under this tab
(curcolumn).
Here is where the real power of the report Data Grid component lies. Each
column contains seven additional properties, four of which are necessary for
configuring the column and its contents and three (or more!) of which can be
used to override default settings made elsewhere.
For example, rather than having a single property containing the header text
for all columns, each column has its own columnheader property. This
property expects a literal value, but also supports square bracket notation
(see a previous issue of Omnis Tech News for an article explaining these
terms). This means that not only can we reassign the header text for a
single column without reference to the others, but we can make the header
text dynamic (data-driven) and, if ::styledtext is kTrue, set the style and
text color for each column individually! Furthermore, if we do set style or
text color using the style() function, it only applies to the one column and
not to subsequent columns as when the entire header is treated as a single
string value.
Let's suppose that we want the header text of the Name column to be a
pleasant raspberry pink color. We would set the columnheader property value
for this column to [con(style(kEscColor,rgb(255,102,255)),'Name')],
including the square brackets. This only has an affect on the current column
and not on columns to the right.
Three column properties (columnfontstyle, columnalign and ::columntextcolor)
can be used to override the style, alignment and text color set by the row
text properties described above. These items apply to the entire column, but
these can be further overridden as we shall see in a moment.
The ::calculation property (note the colons) determines what will be
displayed in a column. As the name implies, this property expects to hold an
expression. The simplest of expressions is a number, literal or variable
name, but more complex expressions are certainly supported. In fact, if
::styledtext is set to kTrue, the style() function can be included in the
expression to override style, color and even justification (using tabs) on a
character-by-character basis. Yes, this is exciting, but let's build up to
it a bit.
If all we want to display in a column is some variable from the list, we
simply place that variable name in the ::calculation property. If we wish to
translate some coded (integer) value to a more intelligible string value, we
would use a pick() function. If we want to combine values, we would use a
con() function. To format a value we might use the jst() function. In
general, we might use a combination of these and other functions to express
the contents of a column.
Suppose we have columns for city, state and region (an integer with values
from 0 to 4 to represent Northeast, Southeast, Midwest, Southcentral and
Western) in our list definition. Further suppose that we would like to
combine the city and state (with an appropriate comma and space) into a
single column and then color code the state to indicate its region. We could
use the following expression as our ::calculation property value:
con(city,',
',style(kEscColor,pick(region,kBlack,kYellow,kBlue,kRed,kMagenta)),state)
Suppose instead that we wanted to combine city and state in the column, but
we would like to right-align the state values into a pseudo-column at the
90-point position instead of separating the two variable values with a comma
and a space. We could use the following expression:
con(city,style(kEscRTab,90),state)
The possibilities seem endless!
There is also a ::columntype property that determines how the column itself
treats its contents. By default, the value given to a column is
kRLclmnAutoSize (constant for a report list component column to autosize).
This works with the ::columnwidth property to determine how wide the column
should be. ::columnwidth is the minimum width the column will take, but it
will expand if the contents require it. If ::columntype is set to
kRLclmnFixedSize, the contents of the column will be truncated if they are
too wide.
There are two options for the ::columntype property that wrap the contents
onto multiple lines. The first is kRLclmnMultiLine. Using this, the text
word wraps onto multiple lines as necessary. It uses the ::columnwidth
property to determine when to wrap and the columnalign property to determine
how the text (as well as the heading) is justified (left, right or center).
The other multiline option is kRLclmnJustText. Using this option, text too
long for a single line (based again on ::columnwidth) word wraps onto
multiple lines, but these lines are full justified. That is, the text on a
line is spread out so that it extends fully to the right and left margins.
The justification of the heading is still controlled by the columnalign
property. (It is a good idea to avoid using tab text escapes with either of
these options as they may yield undesirable results.)
The final two ::columntype options are kRLclmnCheck and kRLclmnCheckCross.
These are used to replace the columns value with a primitive line-drawn
figure. The style of these lines is determined by the checkline appearance
property and can take on any Omnis Studio line style. The "Check" item draws
a check mark if the column value evaluates to a non-zero number or a Boolean
kTrue while the "Cross" item draws an "X" figure if the opposite is so.
These figures take up the entire height of the line including any leading
provided by the rowfontextra property.
If the use of these options temps you, may I suggest a better looking
alternative? Omnis Studio has some great looking icons for the check mark
(1068) and the X (1069). Just use a column with a fixed width of 1 cm
(0.3937 inches), which is the default column width, and the following
::calculation value (where columnVariable is the name of the variable that
determines whether to display a check or a cross):
style(kEscBmp,pick(columnVariable<>0,1069,1068))
This does the same job with a nice color icon that also has a fixed height
so it won't run into the icon on the next line.
So far we have only talked about setting up properties at design time. What
if we need to tweak them a bit at runtime?
Data Grid Notation
We can set General, Text and Appearance properties using Notation as we
would for any other object, bearing in mind that certain rules may apply.
For example, some property values (like colors) must be expressed as either
property constants or numbers (color numbers can be generated using the
rgb() function). Others (patterns, line styles and fonts) must be expressed
as numbers only. Still others (text styles) must be expressed as either the
sum of property constants (kBold+ kUnderline) or the equivalent number (5,
since kBold = 1 and kUnderline = 4).
Column properties are another matter. We can query them to determine what
they are for the column frozen as the current one, but we can't change which
column is current using the notation, nor can we change the value of any of
that columns properties, nor can we directly query the properties of other
columns. What to do?!
The solution deviates slightly from the way we normally work with notational
groups and group properties. A report Data Grid has a special property named
$columnprops. This property is a list that contains the collection of
property values for all columns, but we can neither query nor modify it
directly. Instead, we must do these things by proxy -- by using another list
as an intermediary. Here is how:
Let's name our intermediary list "colPropList". Let's also specify that we
have created an item reference variable named "gridObjRef" to refer to our
report Data Grid field. The first thing we must do is to populate the proxy
list from the $columnprops property value using a method command:
Calculate colPropList as gridObjRef.$columnprops
If we examine the contents of the resulting list, we find that it has seven
columns named for the seven column properties and a number of rows that
equals the number of columns defined for the report Data Grid object. We
could determine what the header text of the third column is by examining
colPropList.3.columnheader, for example. This list allows us to explore any
property of any column.
But we can do more. We can modify the contents of this list as we would any
other list. So we could change the value of the third column header in the
proxy list to "Street Address" by performing:
Calculate colPropList.3.columnheader as 'Street Address'
Of course this does not affect gridObjRef.$columnprops in the least, but we
can do so by replacing the value thus:
Calculate gridObjRef.$columnprops as colPropList
So we can make whatever column property changes we wish in our proxy list,
then replace the $columnprops with the modified proxy list. One other bit of
magic in this process is that if we add a line to our proxy list and then
replace it, the report Data Grid object now contains another column. We
can't change the value of $columncount by direct calculation, but we can add
columns by amending the proxy list.
This has an important implication: if we could construct an appropriately
defined list of column properties to mimic those of a Headed List Box on a
window, we could pass these properties to a report Data Grid and replicate
the headed list on the report...ANY headed list. We just need to work out a
few details...
Making a General Report for Printing Any List Displayed in a Headed List Box
There are two basic things we will need to accomplish this: a method in our
Headed List Box field for determining certain columnar properties and a
report class with a Data Grid object on it and some code (in the report
$construct method) to assign it properties. The report part is easiest, so
let's begin there.
First, we need three variables in the report: a parameter in the $construct
method to receive the list of column properties (pColProps), another
parameter of $construct to receive the list contents (pSourcelist), and an
instance variable for that list so it can be assigned as the data name for
the Data Grid (sourceList). It would also be useful to have an instance
variable of item reference type named gridObjRef as in the discussion above.
The two parameters could be of field reference type to minimize the amount
of RAM required for the transfer. (Nothing worse than three copies of a
large list eating up RAM -- two is bad enough!) We could also pass a title
and other header and footer information for the report, but for now let's
ignore those other needs.
For this report, we only need to print a single record. The Data Grid object
is in the Record Section and it has been given the dataname "sourceList". We
don't require a main file or a main list because we're not relying on those
sources for determining what to print. All we need is the following code in
the reports $construct method:
Calculate sourceList as pSourceList
Calculate gridObjRef.$columnprops as pColProps
Do $cinst.$printrecord()
Do $cinst.$endprint()
Of course, we still need to work out how to create the list of column
properties, but that shouldn't be too hard...
The method in the window that tells the report to print is quite simple. We
just name the report (done here reversibly) and then tell it to print,
passing it the content list and the column properties list. Since parameters
in an execution call can be expressions, we can call the method
($getcolprops) that builds the column properties list implicitly. The code
for the print list command looks like this:
Begin reversible block
Set report name headListPrint
End reversible block
Print report {(listVarname,$cinst.$objs.listObjname.$getcolprops())}
Now for the fun part! We will make the method $getcolprops a method of our
headed List Box field. If we decide to use this with Headed List Boxes
frequently, we can even add this to the (growing list of) methods in the
Headed List Box component in the Component Store. This method will require a
large number of local variables:
propList - a list variable which will eventually contain all the column
properties
columnheader - a character variable which is the first column of propList
calculation- a character variable which is the second column of propList
columntype- a long integer variable which is the third column of propList
columnwidth- a long integer variable which is the fourth column of propList
columnfontstyle- a long integer variable which is the fifth column of
propList
columnalign- a long integer variable which is the sixth column of propList
columntextcolor- a long integer variable which is the seventh column of
propList
calcSource - a character variable that originally contains the Headed List
calculation string
headerSource - a character variable that originally contains the Headed List
columnnames string
widthSource - a character variable that originally contains the Headed List
columnwidths string
colcount - a long integer variable that is used to control a For loop
For simplicity in this example, we will assume that we are doing nothing to
modify style or text color on a columnar basis in the Headed List Box. (We
can deal with this as well in a more general case, but it gets a bit messy.
This is because styles are cumulative in the Headed List Box and not in the
report Data Grid, so we have to keep track of pending style and text color
from one column to the next.) Leaving columnfontstyle and columntextcolor
"empty" (actually zero) will cause the default row properties to be used. (A
quick experiment showed that we can even leave these properties out of the
propList definition, but we'll leave them in for form's sake.)
We also assume that we want all columns to be autosizing. The constant
kRLclmnAutoSize has a numeric equivalent of zero, so doing nothing with this
column will give us what we want by default.
Since we will only be working with one list in this method, we can somewhat
simplify our code by reversibly setting propList as the Current List at the
beginning of the method. (Strictly notational techniques are also fine.) We
must then define the list to contain our seven property columns in the
expected order (see the code below).
Once this basic list setup is done, we then set up our source strings. To
determine the header text, calculation and width for each column, we must
extract the values from the appropriate sources in our Headed List Box.
Since the most efficient way of doing this is destructive, we copy the
original source strings into local variables so the property values in the
Headed List Box itself are not affected. Once this is done, we enter a For
loop that cycles once for each column in the Headed List Box. It is assumed
here that all columns have header text, a width and contents. It is further
assumed that a calculation based on the con() function determines the
contents to be displayed.
Notice that we use $cfield as a notational shortcut to "the field to which
this method belongs". An item reference variable for the Headed List Box
would be equally suitable.
For each cycle of the For loop, we extract the header text, calculation
segment and column width from the source value. Then we remove that portion
from the source value to make the next columns value easier to determine.
This is simplest for the column header text since it is only a
comma-delimited string.
The column width is also such a string, but we have to convert the extracted
value to the proper scale. The value stored in the Headed List Box field is
in pixels, but the columnwidth property in the $columnprops list for the
report Data Grid is expressed in ten thousandths of a centimeter. To convert
this, we need first to divide by the screen resolution (sys(87)) which is
expressed in dpi. This yields a measurement in inches. We then multiply this
by 25401 to convert to ten thousandths of a centimeter. By the way, the
$columnwidths property used as the source reports that actual column widths
seen by the user, which they may have set by resizing the columns. Since our
report Data Grid will autosize all columns, this value will be the minimum
width of the column.
The calculation is a bigger challenge. The source is assumed to be a large
con() function with columns delimited by kTab characters. (If you still use
chr(9), make the appropriate modification to this code.) For our column
calculation value, we extract everything from the beginning of the source
calculation string to the comma before the first kTab and then supply a
closing parenthesis. Even if the column is only a variable name, this will
still work. The source is then modified to remove the beginning characters
of the source string up to and including the comma AFTER the first kTab,
then the 'con(' is replaced at the beginning.
In all of these extraction calculations, a pick() function is used to deal
with the case of the final column when the delimiting character (either a
comma or a kTab) does not exist.
We also use the $getcolumnalign() method of the Headed List Box field to
determine the alignment of the current column.
When we have all the property values determined for a column, we add a line
to the propList variable. We continue until there are no columns left to
process, then we return the completed value of propList to the calling
method. Here is the resulting code:
Begin reversible block
Set current list propList
End reversible block
Define list
{columnheader,calculation,columntype,columnwidth,columnfontstyle,
columnalign,columntextcolor}
Calculate calcSource as $cfield.$calculation
Calculate headerSource as $cfield.$columnnames
Calculate widthSource as $cfield.$columnwidths
For colcount from 1 to $cfield.$colcount step 1
Calculate columnheader as pick(pos(',',headerSource)<>0,headerSource,
mid(headerSource,1,pos(',',headerSource)-1))
Calculate headerSource as mid(headerSource,pos(',',headerSource)+1,
len(headerSource))
Calculate calculation as pick(pos('kTab',calcSource)<>0,calcSource,
con(mid(calcSource,1,pos('kTab',calcSource)-2),')'))
Calculate calcSource as con(pick(pos('kTab',calcSource)<>0,'','con('),
mid(calcSource,pos('kTab',calcSource)+5,len(calcSource)))
Calculate columnwidth as pick(pos(',',widthSource)<>0,widthSource,
mid(widthSource,1,pos(',',widthSource)-1))/sys(87)*25401
Calculate widthSource as mid(widthSource,pos(',',widthSource)+1,
len(widthSource))
Calculate columnalign as $cfield.$getcolumnalign(colcount)
Add line to list
End For
Quit method propList
Again, this can be applied to ANY Headed List Box (that does not use
embedded style() functions).
What If It's Too Wide?
An advantage of a Headed List Box on a window is that it can have a
horizontal scroll bar to view contents that are too wide to be displayed at
one time on the window. Also, the columns can be made resizable to
temporarily hide parts of the content. Reports are static and their
components cannot have controls like scroll bars or resizable headings, so
we have to find other means of viewing contents too wide to fit on the page.
Autosizing columns are useful, but they can cause many lists to become too
wide. How do we deal with this problem?
Omnis Studio allows us to declare that we want to print to multiple pages in
a horizontal direction. We do this by setting the report property $horzpages
to kTrue. This is often not enough to get horizontal page printing to work,
however. For some magical reason, it is also necessary to set the $showpaper
property of the Report Class Editor to kFalse by clicking on the "Show
Paper" tool of the editors toolbar so that it is deselected. Although this
is not a property of the report itself and the setting is not stored with
the report class, this appears to allow $horzpages to work as expected. (It
is also the only way to deliberately set up a report with columns on
additional pages horizontally.)
For best results, the $ignorehpagebreaks property of the report Data Grid
should be set to kFalse. This will cause the horizontal page breaks to
coincide with column breaks rather than splitting the contents of a column
that spills onto the next page.
Summary
We explored a number of concepts and techniques in this article, many of
which have application outside the article's subject matter. I hope you
found it informative.
The primary purpose of the report Data Grid component is to include a list
as part of a record in a report. In this article we pushed the envelope a
bit and eventually treated a list in a single record section as the entire
report. If you need reports based on a list that are column in nature, this
can be a useful technique to consider. There are many presentation features
of this component that the report class itself does not offer.
========================================
I hope you've found this issue of the Omnis Tech Newsletter both interesting
and informative. Please send me your comments and feedback, and include
suggestions for future articles if you like. We would like to hear from
you...
Regards,
--Andrew Smith.
Omnis Tech Newsletter Editor
Email: editor@omnis.net
========================================
No part of this newsletter may be reproduced, transmitted, stored in a
retrieval system or translated into any language in any form by any means
without the written permission of Raining Data.
(c) Copyright Raining Data, Inc., and its licensors 2001. All rights
reserved.
Omnis(r) is a registered trademark and Omnis 7(tm), and Omnis Studio are
trademarks of Raining Data UK Ltd. Other products mentioned are trademarks
or registered trademarks of their corporations.
========================================
To unsubscribe from this newsletter or change your subscription options,
please go to:
www.omnis.net/newsletter