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
The operation of many implicit procedures is tightly intertwined with the system’s run loop for dispatching events. See Understanding the Run Loop for more on this topic.
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.
If you write implicitly triggered code that causes Panorama to malfunction, you’ll need to temporarily disable that code in order to work on that database and fix the problem. See Opening a Database in Diagnostic Mode to learn how diagnostic mode can be used to temporarily disable implicitly triggered code.
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 procedure 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
Note: If the database is opened without any visible windows, Panorama will not run the
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.
Another way to open the database without running the
.Initialize procedure is to use diagnostic mode. See Opening a Database in Diagnostic Mode to learn how this mode is used.
Panorama supports three implicit procedures related to modifying data -
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 that will not 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
.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
.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
.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).
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
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).
.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 procedure 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
.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
If it exists, the .CurrentRecord procedure will be triggered when the user 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 actually strongly recommend avoiding use of the .CurrentRecord procedure altogether if at all possible, 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).
Note: In some situations, the .CurrentRecord procedure will be triggered more than once when the position shifts. For example, this may happen when adding a new record to the database. You should not rely on the ..CurrentRecord procedure being triggered once and only once when the current record changes.
It’s possible to create a log of all changes made to a database, so you can see who changed what when. A 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
.ModifyFill procedures in the database. Here is the
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
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.
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.
formOPEN: ☞ triggered when form is first opened formFRONT: ☞ triggered when form becomes the front window formRESIZE: ☞ triggered when form is resized formGRAPHICSMODE: ☞ triggered when the form switches into graphics mode formDATAMODE: ☞ triggered when the form switches into data mode
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.)
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.
Implicitly triggered code normally runs silently, if it works correctly you won’t be aware of it at all. If code is malfunctioning, however, it may be important to be able to find out how that code was triggered, so that you can track down the problem. If you enable the Debug Instrumentation CodeTriggers option, Panorama will add an entry to the Instrumentation Log every time a procedure is triggered, whether explicitly or implicitly. Whenever a menu is chosen, a button is pressed, or implicit code is triggered, the exact trigger will be noted for later examination. This is especially useful for implicit code since you may not be aware that the code is even being triggered.
Before you can use this feature you have to configure instrumentation on your system. See the Debug Instrumentaiton help page to learn how to set this up.
Once instrumentation is configured, open the Instrumentation panel in the Preferences dialog. Then click on Objective-C Classes at the bottom of the list on the left, then click on CodeTriggers on the right.
With this option enabled, any action that triggers a procedure will be added to the instrumentation log. For example, suppose you have the Help>Font Awesome Icons window open and you click on the button that opens a new window:
If the CodeTriggers option is enabled, two new lines will appear in the instrumentation log:
TextDisplayObject RUN CODE --> call "New Catalog Window" RUN FORM EVENT --> OPEN (Catalog)
The first line records that you clicked on a Text Display Object, and that this object had code associated with it which was run. The log even includes the code itself (if the database is locked down then the code will be redacted). This is an explicit trigger.
The button code opens a new cloned Catalog form. The Catalog form has been set up to implicitly trigger code when it opens. This implicit code trigger has been recorded on the second line of the log (RUN FORM EVENT). If there was a problem in opening the window, the log shows that Panorama got as far as the OPEN FORM EVENT code, so that might be a good place to start looking for the problem.
As this example shows, the instrumentation log not only shows code in your own databases that are triggered, but code in Panorama itself. Much of Panorama is written in Panorama code, so it can be educational to enable the CodeTriggers option and then start clicking on things in Panorama to see what is being triggered.
|10.2||Updated||Implicit triggers can be recorded in Debug Instrumentation log. New form events for triggering code when going in or out of graphics mode.|
|10.1||Updated||Form event procedures automatically trigger when form opened, brought to front, or resized.|
|10.0||No Change||Carried over from Panorama 6.0|