Procedures that are triggered automatically when the user performs some normal Panorama action are said to be “implicitly triggered.” (In Panorama 6 and earlier version this was called a “hidden trigger.”) Examples of user actions that can cause a hidden trigger to activate include adding new records to a database, deleting records, opening a file, bringing a form to the front and many more. The trigger is “implicit” because the user is not explicitly asking Panorama to activate a procedure by pressing a button or selecting a menu choice. Procedures that are implicitly triggered can modify (or even override) the way Panorama reacts to many standard user actions.

Some implicitly triggered procedures are embedded into the property panels for forms, fields or form objects. For example, the Form Properties panel allows you to set up code that responds to form related events.

Other implicitly triggered procedures are simply ordinary named procedures with a special name. This special name always begins with a period, for example .Initialize, which is automatically called when a database opens.

The procedure name must be spelled exactly as described in the documentation below, including upper and lower case. So of these three variations on the word .Initialize, only the first will work.

.Initialize ☞ Ok
.initialize ☞ Will not trigger
.INITIALIZE ☞ Nope

Multi Function Implicit Procedures

Some implicit procedures can perform different functions depending on what the user has done. To identify the code for each function, add a label at the start of the code for that section.

To learn more about labels, see shortcall, goto and info(“labels”).

Triggering code when initializing a database

When a database opens, Panorama checks to see if it contains an .Initialize procedure, and if so, runs it. You can use this procedure to initialize variables, set up custom menus, pre-sort or pre-select the data … anything that needs to be done automatically whenever the database file is first opened.

Warning: Most procedures can only be triggered from the data sheet or a form. However, the .Initialize proce- dure will start running immediately when the file is opened, in whatever window happens to be open. If this window is not a data sheet or form, the procedure may not operate correctly. Many procedure statements (sort, group, select, etc.) will not operate properly from a non-data window. If this may happen, the first thing the .Initialize procedure should do is open a form or the data sheet.

Warning: If several databases are opened at once, Panorama will open the databases first, then run the .Initialize procedures.

Note: If the database is opened without any visible windows, Panorama will not run the .Initialize procedure.

Opening a database without running .Initialize – If you want to open a database but skip the .Initialize procedure, use the File>Find & Open dialog (see “Special Operations Menu” in Find & Open). Locate the database in the dialog, then right click on it and choose Data Sheet Only from the pop-up menu. This will open the database but skip the procedure.

Triggering code when modifying individual database records

Panorama supports three implicit procedures related to modifying data - .NewRecord, .DeleteRecord, and .ModifyRecord.

If a database contains a .NewRecord procedure, that procedure will be called whenever a single new record is added to the database, for example by pressing the Return key or clicking on the Add icon in the toolbar. The info(“trigger”) function can be used to determine which of the three possible actions triggered the procedure:

New.Add ☞ Add New Record tool or menu item
New.Insert ☞ Insert New Record tool or menu item
New.Return ☞ Return key or Insert New Record Below menu item

Here is a typical .NewRecord procedure won’t allow new records to be added if there already at 100 or more records in the database:

if info("records")≥100
    message "Sorry, this database is limited to 100 records."
else
    if info("trigger") = "New.Add"
        addrecord
    elseif info("trigger") = "New.Insert"
        insertrecord
    elseif info("trigger") = "New.Return"
        insertbelow
    endif
endif

Here’s another .NewRecord example that only allows new records to be added to the end of the database, not inserted in the middle. If the trigger is New.Insert, or if the Return key is pressed in the middle of the database, no record is added.

if info("trigger") = "New.Add"
    addrecord
elseif info("trigger") = "New.Return" and info("eof")
    addrecord
else
    message "Sorry, new records must be added"+
        " to the end of the database, not inserted in the middle."
    return
endif
call .ModifyRecord

Warning: The .NewRecord procedure is only triggered when individual new records are added, it is not triggered when multiple new records are appended to the database, for example by importing text.

If it exists, the .DeleteRecord procedure will be triggered when the user attempts to delete a record from the database. This procedure could be triggered by the Delete Record tool or menu item, or by pressing the Delete key in data sheet or view-as-list windows. The example below allows records created today to be deleted immediately, but requires confirmation before allowing older records to be deleted.

if Date < today()
    alertsheet "Are you sure you want to delete this record? "+
        "It was created "+pattern(today()-Date,"# day~")+" ago.",
        "Buttons","No,Yes"
    if info("dialogtrigger") = "No"
        return
    endif
endif
deleterecord

Notice that the record is not deleted unless the procedure deletes the record. The .DeleteRecord procedure interrupts the normal deletion process and takes over. This puts you, the programmer, in control.

If it exists, the .ModifyRecord procedure will be triggered when the user modifies any field in the database. Warning: The .ModifyRecord procedure will not run if a procedure is already running, or if the field has its own procedure (see Automatic Field Code). In those cases you may want the other procedure to call the .ModifyRecord procedure as a subroutine (more on this in a moment). The .ModifyRecord procedure is also not called if the data is modified with a command in the Fill menu, see .ModifyFill below.

The .ModifyRecord procedure example below automatically marks the latest date and time when a record was modified. This example assumes that the database has two fields for time/date tracking: ModifyDate (a date field) and ModifyTime (a numeric field).

ModifyDate=today()
ModifyTime=now()

Note: This example illustrates the .ModifyRecord procedure, but a better way to perform this task would be to use the File>Database Options dialog to designate a numeric (Integer) field as the Time Stamp field. Once a field has been designated as a time stamp field, Panorama will automatically copy the current date and time into this field every time any other field in the record is modified. Setting up a time stamp field allows you to reliably track when each record in the database was last modified. The modification date and time are stored in this field using the SuperDates format.

If your database has other procedures that modify the database they should call the .ModifyRecord procedure to make sure that the time stamp is kept up to date. For example, here is a procedure that automatically subtracts one from the QtyInStock field.

QtyInStock=QtyInStock-1
call .ModifyRecord

Triggering code when a column is modified

If it exists, the .ModifyFill procedure will be triggered when the user uses most commands in the Field>Morph sub menu (Morph, Propagate, etc.). The .ModifyFill procedure will not run if a procedure is already running, or if the field has its own procedure. In those cases you may want the other procedure to call the .ModifyFill procedure as a subroutine (more on this in a moment).

The .ModifyFill procedure example below automatically marks the latest date and time when a fill command is used. This example assumes that the database has two fields for time/date tracking: ModifyDate (a date field) and ModifyTime (a numeric field).

field ModifyDate
formulafill today()
field ModifyTime
formulafill now()

If your database has other procedures that use fill statements they should explcitly call the .ModifyFill proce- dure to make sure that the time stamp is kept up to date. The example below selects all items that have over 500 in stock and have not been touched in 30 days. For those items, it reduces the price by 10%, then marks the modification date and time.

select QtyInStock>500 and today()-ModifyDate>30
field Price
formulafill Price*0.90
call .ModifyFill

Since the .ModifyFill procedure is not triggered by a FormulaFill statement in a procedure, the procedure must update the modification date and time itself by explicitly calling .ModifyFill.

Triggering code when moving to a different record

If it exists, the .CurrentRecord procedure will be triggered when the user when the database shifts to a different record. For example, this procedure is triggered when you move up or down in the database with the vertical scroll bar, or with the Find or Find Next commands. It could be used to initialize a variable or graphics object to display the newly current record. However, we recommend that you keep this procedure as short as possible, and avoid it altogether if you can, since it can cause performance problems. This procedure should definitely not ever change the current window or display a dialog or alert, and it should not ever cause a further shift in the database position (for example doing a further search). It’s possible to get Panorama into an infinite loop where the only way to stop is to force quit the program (or reboot the entire computer).

Logging Changes (Audit Trail) with .ModifyRecord, .ModifyFill and info(“modifiedfield”)

It’s possible to create a log of all changes made to a database, so you can see who changed what when. An Simple Journal example database illustrates this.

As the illustration above shows the Journal field contains a log of all changes made to the database. This log is created by the .ModifyRecord and .ModifyFill procedures in the database. Here is the .ModifyRecord code:

if info("modifiedfield")="Journal" rtn endif
let cellValue=grabdata("",info("modifiedfield"))
let jline="«"+info("modifiedfield")+"»="+constantvalue("cellValue")+
    " on "+datepattern(today(),"YYYY-MM-DD")+" @ "+timepattern(now(),"hh:mm:ss")
Journal=jline+sandwich(cr(),Journal,"")

And here is the .ModifyFill code:

let wasField=info("fieldname")
let jprefix="«"+info("modifiedfield")+"»="
let jsuffix=" on "+datepattern(today(),"YYYY-MM-DD")+" @ "+timepattern(now(),"hh:mm:ss")
field Journal
if datatype(info("modifiedfield"))="Text"
    formulafill jprefix+quoted(grabdata("",info("modifiedfield")))+jsuffix+sandwich(cr(),Journal,"")
else
    formulafill jprefix+str(grabdata("",info("modifiedfield")))+jsuffix+sandwich(cr(),Journal,"")
endif
field (wasField)

One possible problem with this logging mechanism is that the log quickly becomes larger than the actual data! We’re sure, however, that some of you will find this a valuable tool.

Form Event Procedures

Panorama can run your custom code when certain form related events occur, including opening the form, making the form the front window, and resizing the form. Place the code in the Form Properties panel.

This code should include a special label for each event you want to customize. (To learn more about labels, see shortcall, goto and info(“labels”).)

formOPEN: ☞ triggered when form is first opened
formFRONT: ☞ triggered when form becomes the front window
formRESIZE: ☞ triggered when form is resized

The labels are case-sensitive and must match the examples above exactly. Here is an example of form event code that customizes what happens when the form is opened or brought to the front. This example assumes that the window is opened with the openform clone option, so that there could be multiple open instances of this form. When the form first opens, the contents of the ID field are saved into a variable, and the text editor object Letter is opened. Later, if a differnt window is brought to the front and then this window is brought back to the front, the code automatically searches for the record that corresponds to that window, bringing the database back to the same spot. (There is no formRESIZE label, so nothing extra happens in that case. For performance reasons, you should omit any label you aren’t using.)

formOPEN:
    letwindowglobal windowID=ID
    objectaction "Letter","open"
    return

formFRONT:
    windowglobal windowID
    find ID=windowID
    return

Here is what this code looks like in the Form Properties panel.

Keep in mind that this code is only triggered for events involving this form. If you want other forms to be customized, you’ll need to add code to their properties panel. (However, you can use a subroutine to share code between different forms, see below.)

Form Event Procedure Subroutine

If you prefer, you can move the form event code to a separate named subroutine, and call it from the form properties panel. This can be more convienient for editing and debugging, and also allows you to share the same code among multiple forms. If you do this, the code in the form properties panel can only contain one statement – the call statement that calls the subroutine. If Panorama sees just this one statement, it will look in the subroutine for the event labels. If there is more than one statement, the event labels must be in the properties panel.


See Also


History

VersionStatusNotes
10.1UpdatedForm event procedures automatically trigger when form opened, brought to front, or resized.
10.0No ChangeCarried over from Panorama 6.0