[Omnis-Newsletter] Omnis Technical News

omnis-news-admin@omnis.net omnis-news-admin@omnis.net
Thu, 12 Jul 2001 11:22:34 +0100


Omnis Tech Newsletter July 11th, 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.


========================================

***PLEASE NOTE!***

The Technical Newsletter team is taking a summer break, so the next issue
will be mailed on Wednesday 5th September. Have a nice summer!


========================================

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.

In the first article in this newsletter, Geir Fjaerli takes a look at the
code generated by the SQL Form wizard analyzing the methods that log onto
your database. In the second article, David Swain explores the features of
the Omnis Data Grid which allows you to display list data in a grid format
as well as enter data. 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 part 8: Logging on to your Database, by Geir Fjaerli.
-About Omnis FAQs
-The Data Grid Component for Window Classes, 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, now available 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 part 8: Logging on to your Database.
By Geir Fjaerli, Sunshine Data
Email: geir@sunshinedata.net
Web: www.sunshinedata.net

----------------------------------------------------------------------

Welcome to a new part of our Basic Omnis tutorial. After building our first
basic application in the first part, 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.
Part 6: Adding table classes and using our schemas in application windows.
Part 7: What did the wizard build, and a tour of the method editor.

As usual, I strongly suggest that you refer to these parts if you haven't
already, or if you feel uncertain about the details. From part 5 onwards,
each new part will be based on programming done in previous parts, so you
have to follow them all. (For back issues, please go to
www.omnis.net/newsletter and click on the Newsletter Archive link.)

Note: As we go into the depths of Omnis, we will encounter at lot of
features that fall outside the scope of this tutorial, or which we do not
have room to discuss in detail. You will find these discussed in the Omnis
Studio manuals. Please refer to them for more information on subjects that
we only briefly touch.

In part 6 we used the SQL form wizard to create a working window based on
our tables. No hand coding required, we started using our window right away.
Then in part 7 we inspected the window to see what the wizard created. This
also served as a quick guided tour of the method editor.

Our next item is the Task window, where the user can register tasks to be
done and link them to an employee. We shall do this from scratch, manually
adding the components to the window, setting their properties and adding
methods. But first, let us do something we have postponed for a couple of
parts:

THE LOGON WINDOW.
In part 5, 6 and 7 we used the SQL Browser to log on to the database. This
time we are going to add a proper logon logic to our application. Again, we
are going to use an Omnis wizard to help us set this up.

Open you Task library, and double-click the library in the Browser to
display its classes. By now you should have two schemas and two tables, a
Startup_Task and a window. If not, please make sure that you complete the
previous parts.

Open the Component Store. In the group selector (the top pane) select Object
Classes (the one that looks like a red and a blue Lego brick.) This will
display the Object class templates and wizards. They all have the same Lego
icon, so in the standard view it is quite hard to know which is which. You
may want to turn on the text for the Component Store view. (Use the context
menu.) If not, just move the pointer over an icon to see the text appear as
a tooltip.

The last icon is the Session Wizard. Drag this into your library in the
Browser. Name it "oTask" (without quotes) and hit return. The wizard window
will open. This window prompts you for the session template to use for the
object class. You do not have to log on first, this wizard uses the session
template and not the open session. One of the session templates listed
should be TASK. Select this one by checking the checkbox in front of it. We
also want to create a logon window, so make sure that the "Create logon
window" checkbox beneath the list is checked. That is all there is to it.
Click Next, and then Finish.

You will notice that the wizard created a window in addition to the object
class. Let's have a look at them:
Double-click wTask to open it in design mode. You will see that it consists
of a tab pane with two tabs. One for doing the logon, and one for displaying
the tables of the active session. The Logon tab only prompts for user name
and password, the other information is already set up in the object class,
it was copied from our session template by the wizard. Double-click the
window to open the method editor.

In the Variables pane select Instance vars. You will find a single variable
here: iSession of type Object. Have a look at its subtype, it says
TASKS.oTask. This is special for object classes. A variable of type object
is an instance of a given object class. (For a short description of the
various class types refer to part 2.) We tell Omnis which class by selecting
it as the subtype in the variable definition. In this case the wizard linked
the variable to the oTask object class it created.

Note: You may assign the object class dynamically at runtime. This and other
object oriented concepts will be taught by yours truly at this years
EurOmnis conference (www.euromnis.com), together with full Studio training
courses and other topics. The conference is highly recommended, as is its
American cousin GeekWeek (usually in May.)

For now, let us just say that by setting up a variable of type object, we
gain access to all the methods of that object. Each variable has its "copy"
of the object class, so they won't interfere with each others operations or
values.

Why do we do it this way? Why not simply put all the code directly in the
window? Well, here are some good reasons:
1. Reuse. An object class can be used several places in our application. So
rather than duplicate code in several windows and reports, have it one
place.
2. Specialization. If later we want to log on to different databases, we can
create an object for each, and then we just call the correct object, rather
than having to recode all our windows. Again, refer to object oriented
theory to learn more about this.

Now, in the methods list find the Logon $event method, which is the method
for the Logon button. After the event itself (On evClick) there is one
important line:
Do iSession.$logon Returns #F.
This line tells Omnis to call the public method $logon in our iSession
object. iSession was an object with subtype oTask. So this assumes there is
a method in oTask called $logon. That is all the window has to do to log on
to the database, the rest is handled by the object class. The rest of the
method is not important to us now, it enables and disables the buttons, and
lists the tables for the session.

THE OTASK OBJECT EXPLAINED:
Close the window and double-click the oTask object class to see its methods.
We shall focus on two of them: $construct and $logon.

Note: As mentioned in an earlier part, version 3 of Omnis Studio introduced
a new multi-threaded database interface, often referred to as non-visual
DAMs (Data Access Modules). This does not (yet, at least) support OmnisSQL,
so we shall stick to the old way of doing it. In a later part we shall see
how we can modify our application to use the new style DAMs.

In $construct we simply set up the values to use for the logon, including
the following:
Calculate iSessionName as 'TASK'
Calculate iDAMName as 'DOMNIS'
Calculate iHostName as 'D:\Training\tasks.df1'

So, we tell Omnis which session to use, which DAM to use, and the hostname,
which as we remember in the case of the Omnis database is the pathname of
the Omnis data file. (Your path will be different, depending on where you
placed the data file.) Remember that Omnis database doesn't have usernames,
so username and password will be empty.

$LOGON:
Now the method that performs the actual logon is $logon. You will find that
this is a rather complicated method. This is because it attempts to cater
for all supported databases. A better approach might be to have a different
object class for each database. We are going to try and simplify $logon,
explaining what it does as we go through it. When we are through we shall
look at the few lines that are needed for the logon using OmnisSQL.

Here is what happens. The first lines of code builds a list of all open
sessions. Then it tests the name set up by the wizard (TASK) to see if is
unique, and if not it creates a unique session name by adding a number to
the end. It uses a loop (While...) to test the name until the number is high
enough to make the name unique.

Arguably it is not certain that we want to do this. Maybe we would like to
use an existing session if already open. But for now let us assume that we
want our library to use its own unique session.

Note: Remember that our wEmployee window was hardcoded to set the session to
TASK. We will have to modify that later so it used the new session name,
which may be TASK1, TASK2...

The next part of the code sets the session and tries to start it:
Set current session {[iSessionName]}
Start session {[iDAMName]}
It uses the session name set up, and then tries to start the session using
the selected DAM, in our case DOMNIS. If it fails, it brings up an error
message. In OmnisSQL, it may fail because the DAM doesn't exist in the
correct folder. For other databases, the middleware may not exist or be
found. Keep these lines, including the If-End if part.

Since we are using OmnisSQL, the database version is not used. For other
databases, iDBVersion may be important.

Next we set the username, password and hostname to use for the logon. As you
can see, Oracle has its own special syntax here, where all three are
concatenated into username. Again, Omnis database doesn't have usernames, so
username and password will be empty.

Now that we have started the session and set up the values to use, it is
time to try to actually log on, that is connect to the database:
Logon to host (Native API Character Set)
Logon to host (Omnis Character Set)
You will notice that there are two different logon commands here. The
difference is the character set used. Omnis is cross platform, it works on
Mac, Windows and Linux. These operating systems have some things in common,
a lot is different. The Mac operating system has always had a different
character set, so Omnis had its own to make sure applications worked with
both Mac and Window clients on the same database. This works fine for Omnis
applications. But what if you store the data in say Oracle, and try to
access it with another tool? That tool is not likely to understand the Omnis
character set, so the data comes out garbled. Note that this mainly affects
non-English users, the location of the standard A-Z is the same. So you may
optionally use the character set of the platform you are running on instead
of the Omnis one, for example ANSI on Windows.

Note: You can set up character mappings using the tools Omnis supply for the
purpose. Then all data will be converted into the correct character set for
the database on the way into the database, and back again on the way out.

The logon may fail, so we test for that also. Maybe the database server or
network is down, or maybe we typed the wrong password and was rejected. The
OK message may tell us what happened.

If it succeeded, we are logged on to the database.

The logon code still has three more things to do:
1. It sets the transaction mode. OmnisSQL doesn't use transactions, so we
shall leave that for now.
2. It sets the database to use. Some database servers may have several
databases within the same host, and you must select the right one. In the
Omnis native database, the host is the data file, so we can skip this one.
3. Send and initial statement to the server. Usually not required.

So, as we have seen there is a lot of code here that is not really needed if
you only want to log on to Omnis SQL. We could strip our $logon down to the
following code that would get us logged on to most databases:

; Open a session based on the constant variables defined in $construct
Set current session {[iSessionName]}
Start session {[iDAMName]}
Set database version {[iDBVersion]}
If flag true
    Set hostname {[iHostName]}
    Set username {[iUserName]}
    Set password {[iPassword]}
    Logon to host (Omnis Character Set)
End If

Set the session name, and start it. Set database version if required. The
set hostname, username and password. (Username and password can be skipped
for OmnisSQL). The log on with the appropriate character set. That is all.

Of course we would like to get some feedback if our logon failed, so we
would add some error messages to this.

TESTING THE WINDOW:
You may test the window now: Open it in runtime mode from the Browser by
selecting Open window, or from design by Ctrl/Cmd-T. In the open window
click the Logon button. (Using OmnisSQL, we can leave user name and password
empty...) A message should come up telling you that you are successfully
logged on. This will also enable the Tables & Views pane, which lists your
tables and their columns.

INTEGRATING THE LOGON:
Now that we have a working logon window and object class, we need to
integrate them in our application. Two things need to be done:
1. Open the logon window from the Startup_task.
2. Make sure our windows use the correct session.

Open the library in the Browser, and double-click the Startup_Task to
display its methods. Select $construct. You remember that $construct of the
Startup_Task is executed automatically when the library is opened. So this
is where we put our initialization code. So far it is empty. We are going to
add a line of code here to open the logon window.

Click on the first empty line of the method text, and type "open w". The
editor will automatically display the "Open window instance" command. From
the list below select wTask, which is our logon window. After wTask in the
parameters box below the list add /*. The command should now look like this:
Open window instance wTASK/*

This will automatically open the logon window at startup, so we can log on
without having to use the SQL Browser every time. Note that this is only a
quick solution for now. If we don't log on, we can still get into the
application. Later we shall enhance this so that the application only will
work if we have logged on properly.

The second thing we need to do is to change the application so that our
windows use the correct session. As mentioned above, wEmployee was hardcoded
to set the session to TASK. Let us change it to use the correct session
name. To do that, we will just put the session name in a global variable.
Admittedly that may not be such a good idea. What then if later we have
multiple sessions? Well, let us keep it simple for now, and worry about
multiple sessions later.

Open the Startup_Task again and select the Task variables. There are
currently none. Click in the first empty line and add a variable called
tSessionName, and choose the type Character. Omnis will suggest a length of
10000000 characters. That is obviously a lot more than we need, so you may
set it to something shorter. Note that you do not have to worry about that.
An Omnis variable has a variable length, but it will only use as much space
as it needs. So even if it is defined as 10000000 characters, it is not
going to take up several MB unless you actually put that much data in it.
When you have created the task variable, close the method editor again.

Note: You can actually create task variables from the method editor of any
class that has that task set as its design task in the property manager. As
mentioned before, the task variables are shared by all instances belonging
to that task.

Now we need to put the session name into the variable. We should do that
from our logon method. Open the oTask object class and select the $logon
method. Remember that on the top of that method we built a list of open
sessions and tried to create a unique new one by adding a number to the
name? Well, once we had a unique one the method says:
Calculate iSessionName as iUniqueName

iSessionName is the name we log on with, so we can store this in the task
var:
Calculate tSessionName as iSessionName

So tSessionName holds the current session, so let us modify our window
(wEmployee) to use this session.

First we have to do one thing: Select wEmployee in the Browser, and open the
Property Manager. Around the middle is a property called "designtaskname".
This property is used to tell Omnis which task the class "belongs" to.

Note: In reality, a class doesn't belong to a task, the instance (e.g.
opened window) will belong to whatever task it was opened under. But we need
to associate the class a to a given task class so that the correct task
variables are available in design mode. Just be aware that if you later have
multiple tasks, it is possible to open the window under another task, and
then the variables won't be available.

Anyway, in the Property Manager, select Startup_Task as the designtaskname
for wEmployee.

Now, open the window and then its methods (or you can go to the methods
directly by selecting Methods from the context menu in the Browser.) In
$construct, there is currently three lines. The first reads:
Set current session {TASK}

Change it by replacing TASK with [tSessionName], including the square
brackets, so that the line reads:
Set current session {[tSessionName]}

The square brackets tell Omnis to evaluate the argument between them as a
variable name, and use the value for that variable in its place. Now the
window will use the session which name we stored in tSessionName, rather
than being hardcoded to TASK.

So, with today's part we have a proper logon to the database, setting a
session to use.

That is all for today. As usual, comments and questions are welcome. Have a
nice summer!


========================================

About Omnis FAQs

There are several Frequently Asked Question pages located on the Omnis web
site covering a range of topics associated with Omnis, including conversion
of Omnis 7 to Omnis Studio, Omnis Studio for Linux, as well as information
about Raining Data. See the FAQs here:
www.omnis.net/develop/resources/faqinfo.html


========================================

The Data Grid Component for Window Classes
By David Swain, Polymath Business Systems Inc
Email: dataguru@polymath-bus-sys.com
Web: www.polymath-bus-sys.com

----------------------------------------------------------------------

While most list-related field components are intended primarily for display
of list variable contents, the "grid" list-related field components are
intended for data entry directly into a list variable. In this article we
will examine some of the properties and uses of the Window Class Data Grid
component.

This component is closely related to the String Grid component. Data Grids
can represent lists with columns of any displayable data type while String
Grids are limited to representing character columns. For our purposes here
we will consider Data Grid features to be a superset of those of a String
Grid and so include this other component type in our discussion without
mentioning it further (except for exceptions, of course).

Basic Setup

As with a list display field, we must specify a list variable in the
"dataname" property of a Data Grid along with the other basic properties of
a window field. But since it is intended for data entry into the associated
list variable, a Data Grid has no "calculation" property at either the field
(like a Headed List) or the column (like a Report Data Grid) level. If we
leave a Data Grid in its default state ("userdefined" property equals
kFalse), the columns displayed are in the order of their definition for the
list variable.

We will discuss how setting "userdefined" to kTrue changes things in the
"Custom Columnar Setup" section below. What follows in the current section
assumes it is set to kFalse.

To aid in visualizing how the list contents might look at runtime and to
perform other static setup tasks, we can set the "designrows" and
"designcols" properties to appropriate integer values. This will give us a
number of rows and columns that let us see the effect of changing other
property values as we work in the Window Class Editor. When a window
containing a Data Grid is instantiated, however, all columns and rows in the
list variable are displayed. We can limit the number of columns displayed
only by assigning kFalse to "canresizecolumns" and "horzscroll" and setting
the display to show only the columns desired. But if the user gains data
entry access to this field and begins to tab from cell to cell, the focus
will pass to the non-displayed columns even though the user can't see them.
We do have some ways of disabling specific columns, but we will deal with
those a bit later in this article. For right now, understand that setting a
value for "designcols" has no effect on the number of columns that will be
displayed in the instance and that setting "designrows" has no effect on the
number of rows displayed, initial empty lines in the list, or any other such
thing. They simply offer design visualization assistance.

There are several Appearance properties that affect the "look" of the Data
Grid. As with many other fields, there are 3D effects that can be applied.
If this is set to either kShadowBorder or kPlainBorder, a "linestyle" and a
"bordercolor" value can be applied to the outline of the grid. Internal
gridlines are always one pixel wide and are colored by the "cellbordercolor"
property. We can make them "invisible" by assigning them the same color as
the "forecolor" property.

Since the associated list variable may not contain enough lines to fill the
entire grid, the Data Grid distinguishes between the "content" area and the
"grid end" area of its display. These can be given different colors. The
"forecolor" property applies to the "content" area and the "gridendcolor"
property applies to the "grid end" area. There is no "backcolor" or
"backpattern" property for a Data Grid.

A Data Grid field has only very basic text properties. There are only five:
"font", "fontsize", "fontstyle" and "textcolor", any of which can be
overridden by "fieldstyle".

We can specify a "defaultheight" in pixels for the cells in the grid. If we
also set the "autosize" property to kTrue, cells (and their associated rows)
will expand to accommodate up to five lines of text as needed. If this is
not enough space to display the entire contents of a cell, the contents will
vertically scroll when that cell has the focus. (No scrollbar appears,
however.) The contents word wrap within the cell width.

Whether or not "autosize" is turned on, the user can be allowed to change
the width of a column if the "canresizecolumns" property is set to kTrue.
This also allows us to resize columns at design time, overriding the
"defaultwidth" property on a column-by-column basis. If we want to keep our
custom column widths, but disallow this ability for the end user, we can set
"canresizecolumns" back to kFalse before deploying. If we subsequently
decide to change the value of "defaultwidth", Omnis Studio kindly informs us
that we have "made some manual changes to the column width" and asks us
whether we wish to keep them. If we say "Yes", all current columns remain
unchanged, but new columns added will receive the default width. If we say
"No", all columns take on the new default width.

Data Grids can also have column and row headers. A row header is a
non-enterable left column that is used to label each row. We create this by
setting the "fixedcol" property to kTrue. The first column of the list
variable then becomes the row header. Depending on our use, this might
contain a line or record number, a date, a name, or anything that identifies
the row. This header column is automatically shown in kColor3Dface and
embossed (no matter what color and effect has been applied to the field) to
distinguish it from the other content columns. Also, if a horizontal
scrollbar has been applied to the grid, the other columns scroll
"underneath" the header column.

The way in which the row header is used is one of those exceptional
differences between Data Grids and String Grids. In either component we can
set "fixedrow" to kTrue and a column header will appear (looking similar to
that of a Headed List Box). While there is no evHeaderClick event for these
grid components, the column header can make the list display more
presentable. For a String Grid, the content of the first row of the list
variable becomes the column header text. For a Data Grid we have two
choices. We can specify a comma-delimited list of column names in the
"columnnames" property, which acts just like the one for Headed List Boxes.
If we leave this property empty, the names of the variables used to define
the list variables columns become the header text for those columns. In
either case, the first row of the list variable is NOT used as the column
header.

This section has described the "automatic" setup of a Data Grid field. Now
let's see what a little more work can give us.

Custom Columnar Setup

If we set the "userdefined" property of our grid field to kTrue, a number of
useful options become available to us. We now have more control over the
columns of our grid.

First, the "designcols" property becomes more meaningful. Once "userdefined"
is turned on, "designcols" becomes the actual number of columns displayed on
instantiation (still in the order they are defined for the list variable).
Be careful not to make this number larger than the number of columns in the
actual list variable because you will end up with extra unusable columns
from which entered values disappear when the user tabs to the next one.

Beyond our newfound ability to limit the number of columns available to the
user, we also have a number of column properties. The use of these
properties depends on the value of the "currentcolumn" property (like the
"curcolumn" property of a report Data Grid). We can also set the current
column by clicking on it in the Window Class Editor. The column then becomes
outlined in red to indicate it is current.

For example, we can set the "columnwidth" for the current column
independently of the others. By default this takes on the value of
"defaultwidth", but we now have control.

We can also set the justification for the current column by setting its
"columnjst" property value to either kLeftJst (the default value), kRightJst
or kCenterJst. This applies to the entire column, but each column is now
independent of the other.

Forecolor and textcolor may also be set on a columnar basis using the
"columnforecolor" and "columntextcolor" properties. Again, these apply to
the entire column.

Suppose there are columns in our list that we want to protect from
modification. Perhaps we have provided a reference value or a calculated
amount. We can disable the current column by setting its "columnenabled"
property to kFalse (the default is kTrue). Be forewarned that this does not
make the focus "jump over" a disabled cell during data entry. Rather, the
cell receives an "invisible" focus and keystrokes are all rejected. Tabbing
again will move the focus to the next column where it will be "visible"
again if that column is enabled. There may be a way of emulating the "jump
over" behavior, but I haven't found it yet. (More on this under "Events".)

Now we come to the "columntype" property. This gives us four choices as to
how Omnis Studio will interpret the contents of a cell in this column. Our
choices are taken from the four Data Grid Type constants:

kDataGridAutoData - This column type causes the columns data to be displayed
in its default format. For string and numeric data there is no mystery here
(except how to get a thousands separator in large numbers), but date and
time values are another matter. These will be formatted by the appropriate
date/time format variable (#FD, #FT or #FDT). Boolean columns automatically
offer dropdown selection lists for kTrue and kFalse (like in the Sort Fields
window of a Report Class).

kDataGridComboPicker - This column type lets us associate a selection list
for standard data entry choices while still allowing for free text entry
into the cell. The selection list must be specified using the
"columnpicklist" property for the current column. This selection list is not
part of the list variable and should not be included in the definition of
the list. If the selection list contains more than one column, only the
first will be displayed. We have no control over this.

kDataGridDroplistPicker - If the list variable represented by the Data Grid
contains a list variable as one of its columns, this column should be given
the kDataGridDroplistPicker column type. This causes the included list to
display its first column as a dropdown selection list for the current cell
and to display the value from its current line as the value of this cell in
each row of the grid column. Remember that each line of the primary list
contains a separate instance of the included list. To use a droplist picker,
the list for the droplist MUST be included in the definition of the list
variable represented by the Data Grid.

kDataGridIcon - If it is desired to include a column of icons (to show
selection states or for other purposes), the list must include a numeric
column for this. (Long integer is ideal.) If the column type is
kDataGridIcon, Omnis Studio treats the cell value as an icon ID number and
displays the associated icon instead of the number.

Suppose that we don't want to display the columns of our list in their
defined order. Perhaps we defined the list from a Schema Class and the
columns were originally defined in alphabetical order or some other order
that is not appropriate for display. The Data Grid allows us to remap these
columns using the "columndatacol" property for the current column. We do
this by specifying which column number from our list variable we want to be
represented in the current column. For example, if we want the first column
in our Data Grid to represent the third column in the list variable, we
would set "currentcolumn" to 1 and then set "columndatacol" to 3. Setting
"columndatacol" to 0 (the default) is equivalent to setting it to the same
value as "currentcolumn". If you choose to remap columns, be sure all
columns to be displayed are properly accounted for.

Data Grid Notation

We can also modify columnar properties at runtime. We don't have a nice
mechanism like the $columnprops property of a report Data Grid, but we can
manage each columns properties by first setting the $currentcolumn property
to our target column number. In the following examples, assume we have an
item reference variable named "gridRef" that points to our Data Grid field
on the current window instance.

Suppose we want to offer a pushbutton that will set the justification of
column 4 to kRightJst. We must both set the current column to 4 and then
perform our justification change:

On evClick
    Calculate gridRef.$currentcolumn as 4 ;; or Do
gridRef.$currentcolumn.$assign(4)
    Calculate gridRef.$columnjst as kRightJst ;; or use Do and $assign()

We could make other alterations to properties for column 4 once it becomes
the current column. This is now the "state" of the Data Grid with regard to
column properties. If you want to make this more "stateless", use the
Calculate command rather than the $assign() method and do so in a reversible
block thus:

On evClick
    Begin reversible block
        Calculate gridRef.$currentcolumn as 4
    End reversible block
    Calculate gridRef.$columnjst as kRightJst

We can change the width of a specific column in this manner. We can also use
the technique we encountered for Headed List Boxes involving the
$columnwidths property, but we must specify ALL the column widths using that
technique. On the other hand, $columnwidths can be useful as a single source
from which to read the widths of all columns.

Data Entry Properties

During data entry it is sometimes desirable to disallow entry into the cells
of a specific column. We have seen how this is done when the grid is
"userdefined", but we also have a notational technique to use when the list
is "automatic" ("userdefined" equals kFalse). We have a notational method
named $enablecolumn(). This method take two parameters: the number of a
column in the grid and a Boolean value indicating its enabled state. The
second parameter is optional, so the method can be used to read the enabled
state (first parameter only) or set it (both parameters). It only functions
on grids where the "userdefined"
Property is set to kFalse, since the "columnenabled" property can only be
directly overridden as shown in the previous section.

Since a Data Grid is intended for data entry into a list variable, we need
some means of adding new lines to that list variable. This is done by
setting the "extendable" property value to kTrue. For a grid so configured,
a new empty line will be added to the list variable whenever the user tabs
forward into the "gridend" area after the last line of the list.

Events

There is a data entry event named evExtend triggered by this extension. If
trapped in the grids $event method, it includes a parameter (pRow) that
points to the new line. "pRow" is an item reference to that line, so we can
just use other notation to specify aspects of the line. For example, if a
variable named "boolean" is in the definition of our list variable, we can
refer to the "boolean" cell on the new line by pRow.boolean. If we don't
remember the variable used to define column 3, we can still refer to it
using pRow.C3. If we need to know what the line number of the new line is
(perhaps to use it in some other calculations), we can get it from
pRow.$line.

We also have event message variables that tell us about the data entry
process. evCellBefore indicates that a cell is about to receive the focus.
We can know which cell it is because two included parameters (pHorzCell and
pVertCell) report to us the horizontal and vertical grid coordinates (column
and row numbers) of the cell. We can use this information for purposes
ranging from setting default values to limiting the number of lines in an
extending grid.

We have two event message variables that indicate the focus is leaving a
cell. evCellChanging indicates that the focus is attempting to leave the
cell and allows us to react to (and possibly preempt) that action. It not
only includes the grid coordinates of the cell, but a parameter named
pCellData that contains the value entered into the cell. We can use this to
perform data validation checks at the cell level:

On evCellChanging
    If pCellData>999
        OK message  {Value cannot exceed 999}
        Quit event handler (Discard event)
    End If

evCellChanged lets us know that the focus has left the cell. We can't do
anything to prevent this from happening as we did above, but we can still
react and perform other actions as a result.

Scroll Tips

The Data Grid has a very special ability to display tips that assist our
users as they scroll through long lists. These are called "scroll tips" and
they appear next to the "thumb" (sometimes called the "elevator box") of the
scroll bar to display data from the line the list will display if scrolling
stops there.

We turn on this ability by setting either "hscrolltips" (horizontal) or
"vscrolltips" (or both) to kTrue. When scrolling with the Data Grid, a
yellow scrolltip appears next to the thumb to display information about the
scrolling process. For vertical scrolling it displays the value from the
first column of the row that will appear at the top of the grid if that
scroll position is selected. For horizontal scrolling it displays the column
header if "fixedrow" is set to kTrue or the value of that column from the
first row of the list variable if it is not.

We can also detect the scrolltip event and reassign more meaningful
scrolltip text if the need arises. We do this by testing the evScrollTip
event message. This event is accompanied by three event parameters:
pIsVertScroll equals kTrue if the scroll requiring a tip is on a vertical
scrollbar, pScrollPos is the line or column number (depending on which
scrollbar is used) to which scrolling has progressed, and pScrollTip is the
actual scrolltip text (which is passed by reference so we can change it).

An example might help. Suppose that we have a list of people from our
Members file with the first column being their ID number, the second being
the Last Name, the third being the First Name, etc. A basic vertical scroll
tip will display the ID number values, which will appear somewhat random if
the list is sorted by name. It would be better if the scrolltip displayed
the Last Name and First Name separated by a comma and a space. Here is the
code (assume here that our list variable is the current list):

On evScrollTip
    If pIsVertScroll ;; just in case hscrolltips is also on
        Calculate pScrollTip as con(lst(pScrollPos,Members.lastName),',
',lst(pScrollPos,Members.firstName))
    End If

Configuring a Report Data Grid from a Window Data Grid

In our continuing quest to use a single report with Data Grid component from
as many places as possible, here is how we can use it to directly print the
contents of a Window Data Grid. We make the assumptions here that we have a
user defined Data Grid field, that it has "fixedrow" set to kTrue, that
there are no embedded lists, and that real column names are given in the
"columnnames" property. (We also assume you've read the previous articles.)

We can use most of the code from the original Headed List Box example of a
few issues ago. We can still use $columnnames and $columnwidths in the same
way. ($columnwidths gives us the current widths of the instance rather than
the design widths we would be from $columnwidth for the current column.) We
have a $designcols property rather than $colcount for setting the bounds of
our For... loop. Within that loop we set $currentcolumn to the value of our
loop counter to get at column-specific properties (alignment and textcolor).
We then only have to determine how the columns from the list variable are
mapped to the columns of the grid. Here is the code for the $getcolprops
method of our Data Grid field:

Begin reversible block
    Set current list propList
End reversible block
Define list
{columnheader,calculation,columntype,columnwidth,columnfontstyle,columnalign
,columntextcolor}
Calculate headerSource as $cfield.$columnnames
Calculate widthSource as $cfield.$columnwidths
For colcount from 1 to $cfield.$designcols step 1
    Calculate $cfield.$currentcolumn as colcount
    Calculate columnheader as
pick(pos(',',headerSource)<>0,headerSource,mid(headerSource,1,pos(',',header
Source)-1))
    Calculate headerSource as
mid(headerSource,pos(',',headerSource)+1,len(headerSource))
    If $cfield.$columndatacol=0
        Calculate calculation as testList.$cols.[colcount].$name
    Else
        Calculate calculation as
testList.$cols.[$cfield.$columndatacol].$name
    End If
    Calculate columnwidth as
pick(pos(',',widthSource)<>0,widthSource,mid(widthSource,1,pos(',',widthSour
ce)-1))/sys(87)*25401
    Calculate widthSource as
mid(widthSource,pos(',',widthSource)+1,len(widthSource))
    Calculate columnalign as $cfield.$columnjst
    Calculate columntextcolor as $cfield.$columntextcolor
    Add line to list
End For
Quit method propList

Are we having fun yet? Send you comments and questions to me at mailto:
dataguru@polymath-bus-sys.com


========================================

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