OpenInsight 10 and QuickEvent Processing

OpenInsight 10 introduced a couple of changes to how QuickEvents are processed that you may or may not be aware of, so in this post we’ll take a look at them so you can see how they work.

1) QuickEvent arguments are now passed by reference

In previous versions of OpenInsight, arguments passed to QuickEvents were duplicated and passed as copies, which meant that if you altered them in your Commuter Module you wouldn’t see the changes in the calling routine.  As QuickEvents are normally the last item processed in the event chain this is not something you might actually notice, unless perhaps you were using the Forward_Event() procedure from an Event Script.

Event arguments in version 10 are now passed by reference, which means a calling routine will see any changes you make to them.  This is an important point to bear in mind now that it’s possible to alter the order in which QuickEvents are executed in the event chain (see the “Has Priority” flag section below), so be careful when using those arguments in your Commuter Modules.

2) The new “Has Priority” flag

Under normal circumstances QuickEvents are always the last handlers processed in the event chain which normally looks something like this:

  1. Control-specific Event Script
  2. Promoted Events
  3. “System” Promoted Events
  4. QuickEvent

For most purposes this works fine, but there are occasions when you might need to step in front of the system code to do some pre-processing on events such as READ and WRITE, and unfortunately this usually involves writing an Event Script (or a Promoted Event perhaps) to contain your pre-system code like so:

E.g.  Sample pre-WRITE Event Script

Function WRITE( CtrlEntID, CtrlClassID )

   $Insert Logical
   WriteOK = TRUE$

   // Check if we can save the record - if not then WriteOK will be 
   // Set to FALSE$ which will stop the event chain
   GoSub CheckOKToWrite
 
Return WriteOK

However, this removes one of the main advantages of using Commuter Modules in the first place: i.e. the ability to keep all of your code in one place. To alleviate this you could just place your pre-WRITE code in your Commuter Module and call it directly :

Function WRITE( CtrlEntID, CtrlClassID )

   $Insert Logical

   // (Assume Commuter Module ID is the same name as the form with an 
   // "_EVENTS" suffix)
   CommID  = @window[1,"*"] : "_EVENTS"
   WriteOK = Function( @CommID( CtrlEntID, "PREWRITE" ) )
 
Return WriteOK

But then you’re still having to create the Event Script, so you still have a fragment that you need to track.

With version 10 we added a new flag to the QuickEvent handler called “Has Priority”:

QuickEvent definition showing the "Has Priority" flag set

“Has Priority” QuickEvent

If you set this to True the event chain is changed to this instead:

  1. Control-specific Event Script
  2. QuickEvent
  3. Promoted Events
  4. “System” Promoted Events

Which means that you can place something like a WRITE event handler in the Commuter Module, do your pre-processing, and return TRUE$ to allow the system to continue the chain, or use Forward_Event() if you need to do some post processing as well.

Points to note:

  • If you call Set_EventStatus( TRUE$ ) in your Commuter Module QuickEvent handler the event chain is stopped.
  • If you return FALSE$ (“0”) from your Commuter Module QuickEvent handler (and it has to be “0”, not null) the event chain is stopped.

Hopefully this removes the need for Event Scripts for writing pre-system events and should help to keep your applications a little more streamlined and organized, and preserve the benefits of using Commuter Modules.

What is the ENDDIALOG event?

If you’ve been using OpenInsight 10 one of the new features you may have noticed is that forms now have a new event called ENDDIALOG, and in this post we’ll take a brief look at how to use it.

The downsides of modal Dialog_Box programming

There are two primary issues with the classic Dialog_Box/End_Dialog style of programming:

  1. The system has to wait in a loop until the dialog is dismissed, and
  2. This forms a “stack” where the dialogs can only be closed in the order in which they were opened.

Historically the first issue was a bigger problem due to the fact that it tied up the engine thread and maxed out the CPU core it was running on.  This was resolved by moving the dialog “wait loop” into the Presentation Server in version 8, where it could be managed better and the engine thread would effectively “sleep” without consuming resources (prior to this it was a simple Basic+ For/Next loop – very processor intensive).

The second issue still remained however. While it may initially seem logical that modal dialogs should form a “stack” this actually breaks down when they are used from different top-level windows.

For example, suppose I have two instances of the IDE, and I load a modal dialog from the first instance and then another from the second instance.  I can’t close the first before I close the second as the Dialog_Box calls are stacked “inside” the engine.  This is potentially confusing behavior and likely to become a bigger issue if your development moves away from single MDI frames and multiple-monitor setups become more prevalent.

Dialog_Box programming without stacking

The only real way to solve this stacking problem is to break away from the current model of “synchronous” Dialog_Box programming and adopt an “asynchronous” callback model instead.  With this methodology there is no looping at all so the CPU usage stays low and code is only executed as needed, and because the loop is removed there is no stacking either.

The drawback of this approach, however, is that it complicates programming because your code has to be split up into two different sections; one to execute the dialog, and another to respond to the return value.

E.g. Consider a CLICK event handler using the normal approach:

OnClick:
   ... <prepare dialog args> ...
   RetVal = Dialog_Box( "MYDIALOG", DlgParent, DlgParam )
   ... <process RetVal> ...    
Return

… all nice and simple. To do this in an asynchronous fashion would take something like this:

   OnClick:
      ... <prepare dialog args> ...
      Call Dialog_Box( "MYDIALOG", DlgParent, DlgParam  )
   return
   

   OnSomeCallbackEvent:
      // param1 -> DialogID
      // param2 -> RetVal
      Begin Case
         Case ( Param1 == "MYDIALOG" )
            ... <process param2 (RetVal) > ...  
      End Case
   Return

… which is a little more complex.

This approach can actually be taken in any version of OpenInsight, but there’s no framework in place to enforce it and so it ends up needing even more work as the developer needs to find a way to define and target the callback process (E.g. implementing a custom event, using OMNIEVENT, calling a commuter module directly, and so on).

For version 10 we decided to provide that framework so that your code could be structured in a standardized fashion, resulting in some updates to the Dialog_Box and End_Dialog stored procedures and the addition of a new WINDOW event called ENDDIALOG.

Changes to Dialog_Box

Dialog_Box now accepts a new dynamic array argument called “AsyncParams”:

RetVal = Dialog_Box( DialogID, DlgParent, CreateParam, DlgOptions, AsyncParams )

Where AsyncParams is:

<1> An Async flag, to denote it needs to send it's return value to 
    the parent window's ENDDIALOG event

<2> A "cookie" value, which is a simple string passed to the ENDDIALOG 
    event that can be used to identify the returning dialog

e.g.

   AsyncParams    = TRUE$
   AsyncParams<2> = "wibble"

   RetVal = Dialog_Box( "MYDIALOG", DlgParent, DlgParam, "", AsyncParams )

Changes to End_Dialog

This procedure now checks to see if the dialog is in asynchronous mode, and if so it takes the dialog ID, the return value, and the cookie, and sends them to the parent’s ENDDIALOG event, thereby giving us a properly defined framework to implement the callback process.

(Note that the “parent” is considered to be the parent as specified in the originating Dialog_Box call, not the actual runtime PARENT, as these may not be the same thing!).

The new ENDDIALOG event

This event is called from the End_Dialog procedure and accepts three arguments:

DialogID  - name of the dialog that triggered the callback

ReturnVal - the dialog return value passed to End_Dialog

AsyncID   - the cookie value that was passed to Dialog_Box

Example

For the purposes of this example we assume that we are going to launch a simple form called “MY_DIALOG_BOX” using the Dialog_Box function.  The form contains a single EDITLINE control called “EDL_NAME”, and a button called “BTN_OK”.  When BTN_OK is clicked it will get the text from EDL_NAME and return it to the owner with an End_Dialog call like so:

   // CLICK event script for MY_DIALOG_BOX.BTN_OK

   // Get the data the user entered
   Name = Get_Property( @Window : ".EDL_NAME", "TEXT" )
   
   // Return it to the owner window
   Call End_Dialog( @Window, Name )

To launch MY_DIALOG_BOX in asynchronous fashion, do the following from an event on the owner window:

// Launches MY_DIALOG_BOX as a modal Dialog_Box in asynchronous fashion 
// passing it the contents of a variable called CurrName as the CreateParam.
   
AsyncParams = ""
AsyncParams<1> = TRUE$      ; // Async mode
AsyncParams<2> = "GetName"  ; // Optional “AsyncID” param for the 
                            ; // ENDDIALOG event
   
// This code does not halt here - anything the user selects in the dialog
// will be passed back in the ENDDIALOG event 
DlgID = Dialog_Box( "MY_DIALOG_BOX", @Window, CurrName, "", AsyncParams )

In this mode the calling program will NOT halt at the Dialog_Box call and wait for the user to close it. Instead any data returned from the End_Dialog call on MY_DIALOG_BOX.BTN_OK will be passed as an argument to the calling window’s ENDDIALOG event.

The ENDDIALOG event on the parent form would look something like this:

Function ENDDIALOG( CtrlEntID, CtrlClassID, DialogID, DialogValue, AsyncID )
 
// This is an ENDDIALOG event that will be triggered by the End_Dialog 
// call on MY_DIALOG_BOX.BTN_OK when launched in asynchronous mode,
//
// ENDDIALOG is passed three parameters:
//
//   DialogID 
//   DialogValue
//   AsyncID
   
Begin Case
   Case ( DialogID = "MY_DIALOG_BOX" )
      // This is optional but we can check AsyncID if we wanted to have
      //  more fine grain control over how this event is processed.
      If ( AsyncID == "GetName" ) Then
         Call Do_Something_With_This_Name( NewName )
      End
End Case 

Return 0

More details on using the ENDDIALOG event can be found in the online help for the Dialog_Box and End_Dialog procedures in version 10.

Loading the IDE in v10.0.8

Those of you familiar with OpenInsight 10 may know that the way the IDE currently boots and restores previously loaded entities can sometimes be frustrating, mainly because it can repeatedly steal the focus, but also because you can end up with several informational messages which slow the entire process down.  If this is something that you find irritating then you’ll pleased to know we’ve made a change in v10.0.8 so that the IDE now loads invisibly in the background, while the actual boot-up progress is displayed in a splash-screen like so:

IDE Boot screen

IDE Boot screen

Messages are kept to a minimum, and all non-informational ones (like locking failures) are presented in a popup at the end.

These new changes should help provide a welcome and smoother experience when using the OpenInsight IDE in future.

 

 

 

OLE Control improvements in v10.0.8

We added quite a bit of design-time functionality for OLE controls in v10.0.8 so this post provides a quick overview of what’s new:

The CLSID property

This property has a new “editor” dialog.  While it doesn’t allow you to change the CLSID (that’s by design – if you change the CLSID it’s a totally different control!) it does provide you with a lot of information about the control question such as it’s Registry attributes and it’s properties, methods and events, e.g:

OLE Control CLSID General Tab

OLE Control CLSID Properties Tab

OLE Control CLSID Methods Tab

OLE Control CLSID Events Tab

The QualifiedOleEvents property

This property has a new editor that allows you to specify which OLE events you wish to qualify when the control is created, hopefully removing the need for you to do this in code:

OLE Control QualifiedOleEvents Editor

The OLE Properties section

We’ve also added a new category called “OLE” to the properties displayed in the IDE Property Panel, and this contains all of the design-time OLE properties that can be edited for the control:

OLE Properties in the IDE Property Panel

You may also notice that we’ve added some property type-support for editing OLE properties here as well:

  • Color properties can now use the standard Color property editor
  • Fonts can now be edited with the standard Windows Font dialog
  • Enumerated types are edited with a dropdown list showing the “internal” and “external values, e.g:

OLE Enumerated Properties dropdown

Hopefully this will make working with OLE controls and OpenInsight 10 easier for you in the future.

Using Evaluate Notes in the IDE – making a “To-Do” list

One of the goals of OpenInsight 10 was support better integration between the development tools and the Repository, which is why we provided a separate “Repository” tab for entities when they are opened in the IDE, thereby making their attributes easier to view and edit.

This in turn allowed us to expose a Repository feature called “Evaluate Notes”, which has actually been part of OpenInsight since version 2, but was only really used by some system processes (mainly the old and deprecated “Impact Analysis” feature) and provided no user interface to allow editing.  This has now been rectified and is available from the Repository tab as you can see here:

Evaluation Notes

The Evaluate Notes functionality is composed of two parts – an “Evaluate” flag, and the notes themselves. The latter is simply a text field where you may record your notes and have them attached to the entity in question, e.g:

Evaluation Notes editor

The Evaluate flag, when set to True, adds the entity into one of the Repository indexes so that a list of entities with that flag can be obtained quickly.

It is then a simple matter to query this index and obtain a list of all entities flagged “in need of evaluation”, which you can actually treat as a “to-do” list if you wish.  We already provide this list in the IDE by means of the “Evaluate” tab in the “Quick Launch” tool panel as you can see below:

Quick Launch Evaluate Tab

Quick Launch Evaluate Tab

Hopefully this is a feature you find as useful as we do!

Adding Custom Properties in the Form Designer

User-defined properties (“UDPs”) have always been supported at run-time in OpenInsight by giving the desired property a name prefixed with an “@” symbol and then setting a value for it, e.g:

Call Set_Property( CtrlEntID, "@MYPROP_1", "SomeVal" )
Call Set_Property( CtrlEntID, "@MYPROP_2", "SomethingElse" )

Value = Get_Property( CtrlEntID, "@MYPROP_1" )

// etc...

With the upcoming release of version 10.0.8, design-time support for these has been added in the Form Designer via the new “CustomProperties” property.  This is simply a list of UDP  property names and values that can be specified and stored in the Form definition record, which are then processed during form creation to create UDPs that can be accessed in the normal way by Get_Property and Set_Property.

For example, if you enter a couple of custom properties in the Form Designer called MYPROP_1 and MYPROP_2:

CustomProperties Property

Editing the CustomProperties property

You may then use these with Get_Property and Set_Property at runtime, by referencing them with an “@” prefix (i.e. “@MYPROP_1” and “@MYPROP_2”) like so:

// Value will contain "SomethingElse"
Value = Get_Property( CtrlEntID, "@MYPROP_2" )

Two things to note:

1) You don’t need to specify an “@” prefix for the property name in the CustomProperties editor, and

2) You are not limited to simple strings when entering CustomProperties values – you may also use the standard “[,]” syntax for entering dynamic arrays just like you would for QuickEvent parameters:

E.g. to enter an @fm-delimited array you enclose the list of items in ‘[]’ brackets, and delimit them with a comma like so:

An array like this: 

   <1> ItemOne
   <2> ItemTwo

Can be entered as:

   ['ItemOne','ItemTwo']

Note that each array item must be single-quoted, but you can escape a quote in the data by using two single quotes, e.g.

An array like 

   <1> ItemOne
   <2> ItemTwo's Stuff

Can be entered as:

   ['ItemOne','ItemTwo''s Stuff']

For arrays with lower-level delimiters like @vm and @svm you add a set of nested ”[]’ brackets for each level, e.g:

An array like:
   <1>      ItemOne
   <2,1>    ItemTwo_A
   <2,2>    ItemTwo_B
   <2,3,1>  ItemTwo_C_1
   <2,3,2>  ItemTwo_C_2
   <3>      ItemThree

Can be entered as:
   
   ['ItemOne',['ItemTwoA','ItemTwoB',['ItemTwo_C_1','ItemTwo_C_2']],'ItemThree']

And so on.

Hopefully you find this new feature useful and help to reduce the amount of code you need to write.