[Omnis-Newsletter] Omnis technical newsletter
omnis-news-admin@omnis.net
omnis-news-admin@omnis.net
Wed, 28 Nov 2001 13:14:23 -0000
November 28th, 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.
In the first article in this newsletter, Geir Fjaerli continues his tutorial
by showing you how to add the database methods (insert, delete, find, etc)
to your basic Task window. In the second article, David Swain shows how you
can dynamically add and remove menu lines, toolbar tools and other objects
in a window. 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 13: Implementing the data entry interface, part 2 by Geir
Fjaerli
-Omnis Tech notes
-Dynamically Adding and Removing Components 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. Geir is a regular contributor to the
annual Omnis Developer conferences in the US and Europe, speaking about
Object-Oriented programming in Omnis and SQL development, amongst other
things.
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. Latterly, David has
appeared at Omnis Developer conferences providing his own Studio 101
introductory course to Omnis programming and application development.
========================================
Building part 13: Implementing the data entry interface, part 2.
By Geir Fjaerli, Sunshine Data
Email: geir@sunshinedata.net
Web: www.sunshinedata.net
Hi, and welcome back to our Omnis Studio tutorial.
Here is our work so far:
Part 1: Hello World, our first little Studio application
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.
Part 8: The logon window and object class.
Part 9: The task window.
Part 10: The task window continued.
Part 11: Designing a proper data entry interface.
Part 12: Implementing the data entry interface.
You should have those handy, and your "tasks.lbs" library file.
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.)
NEW OMNIS VERSION.
Raining Data has recently introduced a new version of Omnis Studio, labelled
3.1. The main news in this version are Mac OS X and Sun Solaris support.
There are only minor differences to the product itself, and our tutorial
will work equally well with all version labelled 3.something. While I do
encourage you to upgrade, you can use 3.0, 3.01 and 3.02 as well as 3.1 for
this part. We will eventually start looking into the concept of styles, and
these are different in 3.1 from older versions. But for now any 3.x is fine.
(For details about Studio 3.1, please go to: www.omnis.net/products/studio)
WHERE ARE WE?
In our last issue we started implementing our data entry interface. We added
buttons to the top of our window to trigger Find, New, Edit and Delete. This
window will use a modal interface, so these buttons will initiate the modal
data entry. Then we added a Save and Cancel button to terminate the data
entry and perform the actual find, edit or insert. (Delete is different, as
it does not require data entry.)
We also added class methods to the window for the init functions, these were
public methods called $new, $find and $edit. The reason we made them class
methods rather than field methods under the buttons is we may want to
trigger the logic from other controls as well, like a menu or a toolbar. We
also created a couple of subroutines to be called from these public methods.
The Find function will work by creating a list of matching records. We
therefore added a tab pane with two tabs to the window, and put our detail
fields on the first tab. The second tab will hold the list.
Next on the agenda are the following, as we listed in the last chapter:
1. We must add the post entry logic, triggered by the OK (Save) button.
2. We must control our interface better. Today there is nothing that stops
you from pressing the New button, and then, while you are entering the new
task, pressing the Edit or Find button. That of course is not good, so we
need to control the access by enabling and disabling the buttons as
appropriate.
3. We must implement the Delete function.
4. We need a list, on our second tab, to show the result of the find.
But first, let us fix a couple of missing bits:
We have named our window controls to make the easier to reference in our
code. But I see now that we have missed a couple. So open the task window in
design mode, and let us go through the fields and make sure they all have
meaningful names, rather than "wTask_1029" etc. The name of a control is the
first property on the General tab in the Property Manager. So you could
click on each field and look at the name. But there is an easier way:
THE WINDOW FIELD LIST.
In one of the earlier chapters, we looked at the main windows in the Studio
IDE, including the Component Store, the Property manager and the Catalog.
But there are more, and we shall discuss them in relevant situations during
this tutorial. One of them is the Field List. This is a hierarchical list of
all the controls and background objects on a window. To see the field list,
right-click the window in design mode and select Field List from the context
menu.
The list has a line for each foreground or background object on the window.
You will notice that when we first open it, it only shows 5 items, our
buttons and one more. That "one more" is the tab pane. The tab pane entry
has a plus sign in front of it. This means it is an expandable item with
subitems, so if you click the "+" that item will "expand" and show an
indented list of all the items on the tab pane. Click it again to hide the
sub-items.
We call this kind of list control a "tree list". As almost all other
controls in the IDE, it is available to you to be used in your own windows.
This particular tree list is special because it has check boxes in front of
each line. You use these to select an object. This will select the control
on the window as if you had clicked it directly. This can be very useful as
sometimes window controls are hard to find. (You may even have moved it off
screen by mistake. If so, select it in the field list and use the Property
Manager to set its coordinates right again.
Now, using the field list we can easily see which of our fields we have
named and which we forgot. I see a couple of controls that haven't been
named, so we will fix them right away. Select them one by one in the Field
List and then name them in the Property manager:
* The tab pane, which I will name TabPane.
* The drop down or pop up list which I will name EmployeeList. (In fact I
have added both controls, so I will name them with a "dl" and "pl" prefix to
tell them apart: dlEmployeeList/plEmployeeList.)
OK, now our naming is complete, so we can move on with our programming.
METHOD EDITOR
In the remainder of this article you will be adding methods using the method
editor. For a recap on how to add variables and methods to a class using the
method editor, you may like to look at Chapter 4 'Variables and Methods' in
the Using Omnis Studio manual, available to download here:
www.omnis.net/downloads/manuals.html
THE OK AND CANCEL BUTTONS.
You may remember that in our modal interface, our Find, New and Edit buttons
had two main functions:
1. Set a function variable so that when we click OK we know what to do.
2. Enable data entry.
Then in addition, the New and Find buttons would clear the window. This is
our pre-entry logic.
The Save buttons will also have to main functions
1. Disable data Entry.
2. Trigger the code to perform a find, insert or update according to the
pre-entry function selected.
The save button is often referred to as the OK button.
Then we have a Cancel button. This will also disable data entry. But it will
not trigger any find etc.
We have already added these two buttons, but have not yet added any code to
them. Again, we may want to add different controls later, so we will put the
code in public methods rather than directly under the buttons.
Start by opening the method editor for wTask, and add two class methods. The
class methods are listed on top of the method list, before all the field
methods. Right-click class methods and select "Insert new method". Name the
new methods $ok and $cancel.
Then scroll down to the Save button $event method and add a call to the $ok
method:
Do method $ok
Do the same for Cancel's $event:
Do method $cancel
As mentioned above, they both will need to disable data entry. When we
create our pre-entry logic we created a private subroutine for this stuff,
called PreEntry. We will similarly create a subroutine for the post entry
logic. So add a class method again, this time call it PostEntry. For now we
only need two lines of code: We want to set enterable to false, and we want
to reset our iEntryType flag.
Now when we set the "flag", rather than using numbers we used "constants".
You may remember that since Omnis does not offer real user defined
constants, we used task variables and gave them names starting with "tk".
Now we added one for find, one for new and one for edit. But we did not make
any for "none". We could of course just calculate it as zero, but let us
make a constant for this value as well. So in the method editor, select the
Task tab in the variable pane and add a new variable, similar to the other
"constants". Name it tkNone, make it a Number/Short integer and give it the
initial value 0 (zero).
Now add the following method lines to the PostEntry method you just created:
Calculate $cinst.$enterable as kFalse
Calculate iEntryType as tkNone
We shall add to both this and the PreEntry methods later to improve on our
interface, but for now they do the most important stuff, that is make sure
the user can only edit data when they are meant to.
Next we need to call the PostEntry method from our $ok and $cancel methods.
So to both of them add the following method line:
Do method PostEntry
Now this basically is all the Cancel button has to do. But the Save button
has more work still, it must also trigger the appropriate action selected by
the user when they clicked Find, New or Edit:
* Find will trigger a database find (select and fetch) based on the criteria
entered by the user, and build a list of matching records.
* New will trigger a database insert of the new row/record with the values
entered.
* Edit will trigger a database update of the "current" row with the
"current" values, which may have been modified by the user.
So we will add three subroutines to handle each of these. Add them as
private class methods and name them Find, Insert, and Update.
Now that we have the "placeholders" (empty methods) for these we can call
them from our $ok method. $ok already has a call to PostEntry, so we can add
it after that one, right? No, that would not work. Why not? Because we need
to know which of the three to call, and to know that we need to read the
value of out iEntryType flag. But the flag is reset to none by PostEntry, so
it no longer has its value after PostEntry has been executed.
So we need to do it before. I would have liked to disable data entry before
any other code, however, and since that happens in PostEntry as well, we
have a minor problem. In this case it does not really matter, so we will
keep PostEntry as it is, and allow the disable to happen a bit later. But
sometimes in situations like this you will have to change your method
structure.
So in our $ok method click the first and only line and then do Ctrl-I, or
Cmnd-I on Mac. This moves the selected line and any lines beneath it down
one line and creates an empty line above it/them. Similarly pressing
Ctrl/Cmnd-D will delete the selected line(s) and move all lines below it
(them) up. So press Ctrl/Cmnd-I enough times to make room for the following
method lines:
Switch iEntryType
Case tkFind
Do method Find
Case tkNew
Do method Insert
Case tkEdit
Do method Update
End Switch
This construct is called a Switch/Case statement. What does it mean? Well
basically it asks Omnis to compare a given variable to a number of values
and branch to the statement following the one that fits. The user clicked
either Find, New or Edit, and based on that we set our iEntryType flag. Now
we read the flag and branch to the correct subroutine based on the flag
value.
Another way to write this would have been:
If iEntryType=tkFind
Do method Find
Else If iEntryType=tkNew
Do method Insert
Else If iEntryType=tkEdit
Do method Update
End If
Which one to use depends on the number of arguments and the complexity of
them. There are some logical differences between the two, but they are not
important in our case, so I will leave that to your own investigations or
other literature on programming, and move on.
Now that we have our method structure in place, the next action is to fill
the Find, Insert and Update placeholders with some actual code.
Let us start with the simple ones. That would be Insert and Update. Find is
a bit more complex, because we have to create the list field on our second
tab to show the results of the find.
THE INSERT METHOD
We already have an insert method in our application. The wizard created one
in the wEmployee window. If you look at it, it performs the insert with a
single line of code, using the built in $insert of the table class.
The insert method for our wTask window will only need the same single
statement, using the appropriate row variable for this window, which is
iTaskRow. So select the Insert class method and add the following method
line:
Do iTaskRow.$insert()
I recommend that you revisit parts 4 to 7 if you need to refresh your memory
on what the table classes and SQL methods do. In essence, here is what is
happening. When we set up iTaskRow in $construct of the window, we defined
it from the tTask table class. tTask is based on the sTask schema which
holds the column structure of our database table. So when we open the
window, iTaskRow is set up as an instance of the table class, and therefore
has the columns of sTask. Being an instance of the table class, we can use
any of the methods in the table, built in or user defined, with our row
variable. $insert is one of these built in methods, and says "Insert a new
row into the database table which sTask is linked to, using the current
values in iTaskRow".
THE UPDATE METHOD.
Now select the Update class method to create the code for updating a row.
Update is almost as simple as insert. Our tTask table class has a built in
method for updates as well. But there is one difference: Since we are
updating an existing record, we need to identify the record to update.
Remember that all the work we have done is in the application window, in
effect on a "local" copy of the data stored in the database. There is no
"live link" between the local copy and the stored data, it is up to us to
make sure we update the correct one.
But how do we know which was the original row? We cannot simply compare to
iTaskRow, because that will have been modified by the user by now. This is
the purpose of our iOldRow variable. It contains the original (stored)
version of the data at all times, so no matter what changes we have done to
iTaskRow, we can use iOldRow to find the original.
We do this by passing iOldRow as a parameter to $update. So our method line
will look like this:
Do iTaskRow.$update(iOldRow)
Now this will obviously only work if iOldRow is identical to the stored data
at that time, so we need to make sure it is. This means that any time the
data in the database changes in any way, we need to update iOldRow. When
does the data change? Well it changes in all our database actions:
* When we find a record.
* When we insert a new record.
* When we update a record.
* When we delete a record.
I the first three - find, insert and update - we have changed the current
row in the database, by finding another one, by inserting a new one or by
modifying the contents of the current one. So we need to update iOldRow to
reflect this. When we delete a row, there is no current database row, so we
should clear iOldRow as well.
So in our Update and Insert methods, add the following method line after the
existing lines:
Calculate iOldRow as iTaskRow
This makes sure that iOldRow matches the current version of the data. We
will add the same to the Find method when we create it as well.
Having added the calculation to both the Insert and Update methods, we have
completed these methods for now. I say for now, because our interface is
still a bit rudimentary. We shall refine it as we get further down our
development job.
But first, let us start working on our Find function.
THE FIND.
We said earlier that we would use a list to display the results of our
finds. Basically we need to decide which criteria to find on, and then
create logic to handle those finds. Depending on the number of tasks we have
stored and the complexity of our finds, we may end up with anything from
zero to many matching tasks when we execute our find. Say the first thing we
do when we get to the office in the morning is we ask for all tasks that has
"date planned" set to today. There may be nothing planned for today, only
one task, or a number of tasks.
This will of course affect the way we present our data. If there are many
tasks matching our find, we will list them all so the user can select the
correct one. If there is only one, there is no point in putting it in the
list, then we may simply display it directly in the details window. And if
there are zero, we might want to display a message saying none was found.
The other important decision is what fields do we want to find on. In many
data structures only some of the fields are relevant to find on. In a
customer table, you will want to find on customer name and zip code, but
probably not on post office box number. If you are doing telesales, then
finding on phone number may be relevant, for others it may not be.
In our task table, there is quite a few search criteria that could be
interesting:
* All tasks on a given date for a given employee.
You would use this to find say all your own tasks for today.
* All tasks of type meeting with date less than Friday this week.
A secretary would use this to see who is in the office or not this week,
maybe to plan a staff meeting.
* All tasks where date done is greater than date planned.
A manager would use this to find tasks that were not completed as planned.
This can easily become very complex, and may require a more advanced
interface than our simple window. For now we will use the find button to
simply locate tasks for our own use. Here are the criteria we will enable:
1. Equals date planned. All tasks planned for the given date.
2. Equals employee selected. All task for the given employee.
3. All tasks where the task description begins with what was typed.
To make things even simpler, we will allow searches on a single criteria
only. Later we can enhance our search so that we can search on multiple
criteria, and allow for more complex searches.
THE WHERE CLAUSE.
We are using SQL in our application. To be able to do finds, we need to
understand a little bit about SQL. As I have said earlier, I will not be
discussing SQL syntax in detail in this tutorial, there are a number of good
books and other sources on that subject. But we need at least to get you
started, so in part 4 I presented a quick intro. Please reread that chapter
if you are not familiar with SQL.
Now basically, to find a row (or rows) in SQL you use a select statement.
These can be very complex, but a simple version would be something like
this:
"Select Task.ID, Task.DatePlanned, Task.Task from Task where Task.ID = 123"
As we see, this statement consists of 3 parts:
* The column list defines which columns from the table(s) we want to return.
* The "from" part tells us which table(s) to select the rows from.
* The "where" clause tells us the criteria to search on.
No when we use the built in $select of the table class, it takes care of the
first two for us, assuming we have a single table find. All we have to do is
build the where clause, and add it like this:
Calculate lWhereClause as "where statement..."
Do listvar.$select(lWhereClause)
As we can see from the example above, the where clause is basically quite
simple:
"where table.column = value"
The comparison operator does not have to be "=", we can use <= (less than or
equal to), LIKE (for pattern matching) and a number of other criteria. We
can also chain conditions with AND or OR, like this:
"Where table.column1 = 123 AND table.column2 LIKE 'Smith'
We have decided that we want to select on a single criteria only. How do we
pick the right one? Well, since the Find button cleared all fields before
the user started typing their criteria, we will just use the first one that
has a value. If something was typed in the Task description, find on that,
if not see if a date planned was filled in.
So now we are ready to build our where clause method. That however will have
to wait until next time. I hope you enjoyed today's lesson, and hope you
will be back next issue for more.
In the mean time, take care!
========================================
Omnis Tech Notes
The Omnis web site provides a number of resources in the Support area,
including Tech notes. The Omnis Tech Notes are designed to help you with
some of the more common technical problems you might face while developing
your Omnis application, and include topics: Conversion, Omnis Web Client,
Notation, Printing and Reports, and more. Click here to get a complete list
of tech notes:
www.omnis.net/develop/resources/notes/technotes.html
========================================
Dynamically Adding and Removing Components
By David Swain, Polymath Business Systems Inc
Email: dataguru@polymath-bus-sys.com
Web: www.polymath-bus-sys.com
------------------------------------------------------------------
In the last issue of Omnis Tech News, I referred to the process of
dynamically adding menu lines, toolbar tools and other objects to a window
instance. I apologize if this may have caught you off guard. This is an
important technique in Omnis Studio. It also makes use of principles that
must be understood before we begin exploring the world of Component Store
Wizards and how to modify them. Since that is the next logical subject in
this series on modifying the contents of the Component Library, this is an
appropriate time to explain this technique and associated principles.
Generic Components
As I mentioned in the last article, I have created a number of generic
components for use in my Omnis Studio work. These are classes designed to
perform similar work in a variety of similar situations. For example, I have
created a generic "Record menu" menu class and a generic "Record tools"
toolbar class that are designed to be installed in the menu bar and tool
docking area of most windows in my libraries in lieu of using pushbutton
objects on the window itself. These contain controls that access the most
commonly used methods for manipulating records on a window instance. The
methods in these controls are written to invoke the appropriate public
methods of the window instance on which they are installed.
Not every window on which these items are installed needs all the available
commands, however. Sometimes I need to remove a few of them during the
$construct() of the window. The reason may be that end users with certain
access levels are granted only browsing access, so commands that insert,
update or delete records are not appropriate for them. Or the reason may be
that some commands are simply of no use on certain windows, like a "Find"
command on a window where records are selected from a list. In these cases,
I have chosen to completely remove those useless commands from my menu and
toolbar, rather than merely deactivate them, for a cleaner look.
Conversely, on other windows a few additional menu and tool items may be
needed to access other public methods of the window. In cases where only one
or two such methods need menu and toolbar access points, it makes more sense
to dynamically add an item or two to my generic menu and toolbar instances
than to create a special menu and toolbar class for this single use.
It is this second technique of dynamically adding items to a group that is
the focus of this article, although I will also describe the removal of
items later on. First we need to discuss the concept of item or object
groups.
Group Properties and Methods
Groups of objects have an identity as well as properties and methods just
like individual objects. As you might remember from discussions we've had
about Omnis Notation, we refer to an object notationally by first
identifying the container within which the object resides, then by the item
group it belongs to within that container, and finally by its name or
identification number. Consider how we might point to the "Next" line of the
"Record menu" installed on the current instance of a window:
$cinst.$menus.Record menu.$objs.Next
"Record menu" belongs to the group of all menus installed in the current
window instance. The menu contains a group of objects ($objs is how we refer
to the lines of the menu) and this group contains the line whose name is
"Next".
Rather than continuing the notation string to a specific item in a group, we
can use notation to invoke a method of that group. There are ten methods
that all modifiable groups have in common: $count(), $makelist(),
$appendlist(), $insertlist(), $sendall(), $first(), $next(), $findname(),
$add() and $remove(). Non-modifiable groups (like the group of hash
variables - $hashvars) do not include $add() and $remove(). Some groups have
a few additional methods, but they are variations of this basic set (like
$addbefore() for a toolgroup). To invoke the $count() method of the "Record
menu objects" group mentioned above, we would use:
$cinst.$menus.Record menu.$objs.$count()
This method returns the number of items in the group. (I prefer to think of
this one as a property, although it is technically a method and requires the
parentheses.) We don't have time to detail all these methods in this
article, but we will examine in detail the use of $add() and $remove() for
menus and toolbars installed on a window.
The $add() Method
In one of the scenarios listed above, it is necessary to add one or more
members to a group of objects. To do this, we employ the $add() method for
the group.
Some built-in methods, like $assign(), simply return a value indicating the
success of the operation they perform. Function-style and property-style
methods return an answer or value of some sort. The $add() method for an
object group returns an item reference to the new item it has added to the
group. We will see a bit later that this is a very useful aspect of the
method.
Many built-in methods require parameters to properly perform their tasks.
The $add() method is one of these. The problem is that different groups
require different parameters for adding a new member. (If it helps, consider
what it takes to join various groups in real life - there can be differences
in membership requirements or annual dues, etc.) In order to know what
parameters to provide the $add() method, we must learn what parameters are
required for the specific group we wish to augment. We can do this in a few
different ways.
The $desc Property
Omnis Studio is very extensively self-documented. A brief description of
every built-in property and method is included within the development
version of the program. A methods description contains the proper syntax for
using the method and indications of the datatype for each parameter and
whether certain parameters are optional. The trick is to know where to find
these descriptions.
The properties and methods of individual items are available through the
Interface Manager in the Method Editor, but not the methods of an item
group. To access this information, we must "drill down" to the item group
using the Notation Inspector and then examine the methods for that group
using the Property Manager. The method names are shown in the left column of
the Property Manager window and the parameter information is shown in the
right column. Of course, since the parameters can't be edited, we must
significantly widen the Property Manager window to see the entire parameter
description string of some such methods.
We can also view the complete contents of the description property of these
methods by turning on the "Help Tips" feature of the Property Manager
window. (Context-click on an open area of the window and select "Help tips"
from the context menu presented.) With this feature activated, when we pause
the mouse pointer over a method or its parameter string in the Property
Manager window, a help tip appears showing both the proper syntax for the
method and a brief description of what the method does, including any return
value it yields. This is great if you have a good memory or can write the
information down, but there is another way to access this information that
offers the opportunity to cut-and-paste the description.
We can directly capture the $desc property of a method by calculating some
character variable as
notationToGroup.$methodname.$desc
We can then open the value window for that variable (context-click on the
variable name in either the Method Editor or the Catalog and select the
first line of the menu that appears), select the entire text, copy it and
paste it into any convenient location (like a work processor document).
We can also use the $makelist() method to list them all:
Calculate #L1 as notationToGroup.$methods.$makelist($ref.$name,$ref.$desc)
Of course, if we want to capture this information for an instance, then we
should launch a custom method that performs this task from the instance in
which we are interested. In my example above, I place a pushbutton on the
window with a method behind it that contains this code:
On evClick
Calculate #S1 as $cinst.$menus.Record menu.$objs.$add.$desc
The text that appears in #S1 can then be copied and pasted here:
$add(cText,bEnable,bCheck) adds a new line to the menu and returns an item
reference to it
This tells us that the $add() method for adding a line to a menu installed
on a window requires three parameters: a character value that will be the
text to appear as a label on the menu line, a Boolean value indicating
whether the line should be initially enabled and another Boolean value
indicating whether the line should be initially checked. None of these are
optional according to the syntax given. (Optional parameters are enclosed in
square brackets in the syntax display.) We also learn from this that the
method returns an item reference to the newly created line. Since the line
only has text and a couple of properties set, this is good news! We can use
this reference to modify some of the new items other properties, including
giving it a name and providing it with some keyboard shortcuts and some
method text. (Nothing more useless than a labeled menu line with no method
to execute!)
If we perform the same exercise as above, but this time for our "Record
tools" toolbar (using "$cinst.$toolbars.Record tools.$objs.$add.$desc"), we
get the following text:
$add(type,{cText,iIconid,bEnable,bCheck|calc,iIconid,iWidth}) inserts a
control into a toolbar and returns an item reference to it
This set of parameters is a little more interesting and requires a bit more
explanation. The curly brackets indicate a choice that must be made between
two sets of parameters. (Notice the vertical line character used to
represent the OR operator.) The first parameter is the "type" of toolbar
item we wish to add. We must specify this using one of the "Toolbars"
constants. There are thirteen of these, including the toolbar spacer. All
their names begin with "kTool". If we want to add a spacer to a toolbar,
only the first parameter (kToolSpacer) is required. Any other type of tool
additionally requires EITHER labeling text (character), an icon id (long
integer), and enabled and checked indicators (Boolean) OR a calculation
expression and an icon id and width in pixels (both long integer). The
choice depends on the type of tool being added.
The $add() method is very powerful in that it brings a completely new item
into existence. Unfortunately, it is only the "shell" of an item and we
usually must modify additional properties of the new item to make it fully
functional. That's where the item reference returned by the $add() method
comes in handy. Let's flesh out the example I referred to at the beginning
of the article to illustrate how this is done.
The Actual Example
Suppose we have a window class for viewing Customer information and we have
decided to use our generic "Record menu" and "Record tools" embedded classes
to access the basic public methods for record manipulation. (For the OO
police out there, these methods may in their turn invoke methods of a data
object instance or a row variable defined from an SQL class to perform the
actual work - a subject for yet another time...) But this window has an
additional public method we would also like to access in the same way: a
method for opening a window to display a list of invoices for the current
customer. We want our newbie users to have menu and toolbar (with nice
tooltips) access to this method while our power users have a keyboard
shortcut (Cmnd/Ctrl-I, for example) associated with the menu line. (N.B.: I
use "Add" instead of "Insert", so "I" has not been used for anything else.)
We will add the additional menu line and toolbar item in the windows
$construct() method.
To set up for this process, we need to define a few variables. First we will
set up two instance variables (because we may need these references many
places within the window instance) of "Item reference" type in the window
class named "menuObjRef" and "toolObjRef". We will default the first to
"$cinst.$menus.Record menu.$objs" and the second to "$cinst.$toolbars.Record
tools.$objs". Notice that we do not need to follow these with a ".$ref" when
assigning their value within the Variables Pane. Next, we will define two
local variables (because we will only need these references within the
$construct() method) of "Item reference" type in the windows $construct()
method named "newObjectRef" and "newMethodRef". We will not assign values to
these in the Variables Pane, but will use them to receive item references
returned by group $add() methods within our code.
Now we begin building our code in $construct(). The first thing we need is a
command to create a new menu line for "Invoices" that is enabled and not
checked. We also want to capture the item reference returned by the $add()
method in "newObjectRef" so we can perform other operations on this new
item. A suitable line would be:
Do menuObjRef.$add('&Invoices',kTrue,kFalse) Returns newObjectRef
Notice that since the $add() method allows me to specify the "text" property
for the line, I can include an ampersand (&) character to serve as a Windows
alt-key equivalent (which assumes the title of the menu also has an alt-key
equivalent).
When using the $add() method, the new item is automatically appended to the
end of the group, in our case to the bottom of the menu. If we want to place
this line elsewhere within the menu, we can use either the $addbefore() or
$addafter() methods. The only difference in using these is that their first
parameter must be a notational reference to the "anchor" item on which we
are basing the relative addition. The normal parameters from the $add()
method then follow that one. So if we had wanted to add our new menu line
before the "Find" line, we would have used:
Do menuObjRef.$addbefore(menuObjRef.Find,'&Invoices',kTrue,kFalse) Returns
newObjectRef
Wherever we choose to add it, our new menu line is still a few properties
short of being useful. It doesn't have a name, it has no keyboard
equivalents and, most importantly, it has no $event() method so it can't
execute any code. We can assign a name using the item reference that was
returned as follows:
Calculate newObjectRef.$name as 'Invoices'
The shortcut keys must be assigned for each platform as follows:
Calculate newObjectRef.$macshortcutkey as kCommand+asc('I',1)
Calculate newObjectRef.$winshortcutkey as kControl+asc('I',1)
Calculate newObjectRef.$unixshortcutkey as kControl+asc('I',1)
Finally, we need to add a method named "$event" to our object. But wait...we
haven't determined what parameters are needed by the $add() method for the
$methods group within our new menu line. A quick exploration yields the
following description:
$add(cName) adds a method and returns an item reference to it
So we only need to supply a name for the method and capture the item
reference returned by the $add() method, like this:
Do newObjectRef.$methods.$add('$event') Returns newMethodRef
Now we have a method named "$event", but it contains no code. Methods have a
property named "$methodtext" that contains the entire text of the method.
For our purposes here, assigning a value to this works just fine, especially
since our method is a one-liner. We just
Calculate newMethodRef.$methodtext as 'Do $cwind.$invoices()'
Notice that we refer to the window instance containing the "$invoices"
method using "$cwind" and not "$cinst". The reason is that "$cinst" refers
to the current instance of "Record menu" when used from a method of a menu
line. "$cwind" refers to the overall window instance within which the menu
instance is embedded. This is also used from within subwindows to refer to
the main window within which the subwindow is embedded. A legitimate case
can also be made for using "$topwind" since this window must be the top one
if we can select a line from an embedded menu, but "$cwind" is more precise.
So, the complete method code for adding the menu item is:
Do menuObjRef.$add('&Invoices',kTrue,kFalse) Returns newObjectRef
Calculate newObjectRef.$name as 'Invoices'
Calculate newObjectRef.$macshortcutkey as kCommand+asc('I',1)
Calculate newObjectRef.$winshortcutkey as kControl+asc('I',1)
Calculate newObjectRef.$unixshortcutkey as kControl+asc('I',1)
Do newObjectRef.$methods.$add('$event') Returns newMethodRef
Calculate newMethodRef.$methodtext as 'Do $cwind.$invoices()'
Adding the toolbar item is very similar, but there are some slight
differences. First, let's set the new toolbar item apart from the rest by
adding a spacer:
Do toolObjRef.$add(kToolSpacer)
If we want to name the spacer (a good practice when working with the class
in design mode, but less useful when dynamically adding a spacer to an
instance), we would have captured the item reference and used that to modify
the $name property.
Next we want to add a "Command Button" item to the toolbar. I normally don't
use labeling text on toolbar items, so we'll leave the text parameter empty.
But I do use icons, so we'll include the icon number for an icon that looks
like a piece of ruled notebook paper (1091). We want the tool to be enabled
by default, but not checked. These both default to "kFalse", so we'll set
the first to "kTrue" and treat the other as optional. The resulting code
line is:
Do toolObjRef.$add(kToolCommandButton,'',1091,kTrue) Returns newObjectRef
We now need a name and tooltip text for the tool:
Calculate newObjectRef.$name as 'Invoices'
Calculate newObjectRef.$tooltip as 'Invoices'
Next, we need a $event() method:
Do newObjectRef.$methods.$add('$event') Returns newMethodRef
And finally, we need some code in the method:
Calculate newMethodRef.$methodtext as con('On evClick',kCr,'Do
$cwind.$invoices()')
Notice that this $event() method requires two lines. A menu line can only be
invoked, so an On command is unnecessary. But a toolbar element can react to
a number of event, so we have to specify which event will invoke our method
(in this case, a click). We could have gone the laborious route and added
each line, supplying text to it before adding the next line. But we can also
just add multiple lines of text separated by carriage returns as we did
here. For more complex methods, there is a third technique. We could use the
"text block" commands and place an entire block of text into a variable,
then calculate the $methodtext property as that variable.
The complete method code for adding a toolbar item is:
Do toolObjRef.$add(kToolSpacer)
Do toolObjRef.$add(kToolCommandButton,'',1091,kTrue) Returns newObjectRef
Calculate newObjectRef.$tooltip as 'Invoices'
Calculate newObjectRef.$name as 'Invoices'
Do newObjectRef.$methods.$add('$event') Returns newMethodRef
Calculate newMethodRef.$methodtext as con('On evClick',kCr,'Do
$cwind.$invoices()')
Removing Items
The Invoice List window opened by the $invoices() method in the above
example is a browse-only window based on a list. We have made no provision
for inserting, updating or deleting records on this window and users locate
a record by finding it in the list, so no "Find" command is needed. The
"Next", "Previous", "First", "Last" and "Print" commands are still viable,
however, so we will use our generic "Record menu" and "Record tools"
embedded classes and just remove the unnecessary items from them.
We do this with the $remove() method for the item group. This method always
requires a notational reference to the item to be removed as its parameter.
Unlike the $makelist() and $sendall() methods that use "$ref" to refer back
to a generic member of the group, $remove() requires a complete notational
reference to a specific item. We can still use instance variables of item
reference type as we did in the Customer window to shorten these notation
strings.
For example, if we want to remove the "Find", "Add", "Change" and "Delete"
items from both the menu and the toolbar, our $construct() method would
include the following lines:
Do menuObjRef.$remove(menuObjRef.Find)
Do menuObjRef.$remove(menuObjRef.Add)
Do menuObjRef.$remove(menuObjRef.Change)
Do menuObjRef.$remove(menuObjRef.Delete)
Do toolObjRef.$remove(toolObjRef.Find)
Do toolObjRef.$remove(toolObjRef.Add)
Do toolObjRef.$remove(toolObjRef.Change)
Do toolObjRef.$remove(toolObjRef.Delete)
Do toolObjRef.$remove(toolObjRef.Spacer1)
Do toolObjRef.$remove(toolObjRef.Spacer2)
Do toolObjRef.$remove(toolObjRef.Spacer3)
Notice that we also removed three of the four spacers from the toolbar since
multiple adjoining spacers look a bit odd. By naming the spacers in some
logical manner when creating the original class, we save ourselves the
headache of remembering their associated identification numbers when we want
to remove them.
I hope this has been a worthwhile exercise for you. It takes almost no time
for these lines of code to run, but it takes quite a bit of text to describe
what they do and why.
========================================
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 Technical 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