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:
- The system has to wait in a loop until the dialog is dismissed, and
- 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:
... <prepare dialog args> ...
RetVal = Dialog_Box( "MYDIALOG", DlgParent, DlgParam )
... <process RetVal> ...
… all nice and simple. To do this in an asynchronous fashion would take something like this:
... <prepare dialog args> ...
Call Dialog_Box( "MYDIALOG", DlgParent, DlgParam )
// param1 -> DialogID
// param2 -> RetVal
Case ( Param1 == "MYDIALOG" )
... <process param2 (RetVal) > ...
… 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
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
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:
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 )
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.