Tech News Back Issues Issue: 081105

Closing Instances and Tasks

By David Swain
Polymath Business Systems
www.omnistraining.com
dataguru@polymath-bus-sys.com

As programmers, we spend a great deal of time and effort on setting up the windows in our applications and managing events that occur while a window is being used. But most of us probably don't give much thought to what happens when such a window, or the task to which it belongs, is closed. If not, we may be missing an opportunity to add functionality to our applications. Omnis Studio gives us some useful facilities for the instance endgame.

Why Worry About This?

Omnis Studio offers us a number of options surrounding the closing of instances, but these are subtle and not forced upon us. Except for occasional concern about premature destruction in the midst of a data entry process, many developers are content just to let an instance close. Out of time, out of mind. We certainly have the opportunity to decide whether the instance should be allowed to close in the first place, but we can also monitor and/or manage the closing operation and do so with some knowledge of how that closing was invoked.

For example, we may have some windows that we do not want the user to be able to close during certain operations. With a little forethought, we can set up conditions that indicate these instances are "busy". We can then detect these conditions when the window (or other type of instance) is about to close and prevent it from happening in those cases. Or instead of preventing the close from happening, we might instead save the user's work in progress in some fashion and then set a flag in the database that indicates this user should be returned to that state when an instance of this window is again opened by this user. We could also save such settings as the position and size of the window and other configuration options so that our user can return to a familiar setting on the next visit.

Many seasoned Omnis Studio developers will be familiar with some of these options. But a few new possibilities may reveal themselves as we explore the process.

Paths to the Exit

Let's begin by considering the closing of window instances. Window instances contain an event handling mechanism with which we are all more or less familiar. That set of options includes part of the window instance closing process we intend to explore here. But there are more tools at our disposal than just the event handler - and we can use the others in instances that are not directly part of the event handling cycle.

There are at least five ways the user can cause a window to close. The user can:

  1. Click the close box on the title bar of the window (or perform the keyboard equivalent Command/Control-W)
  2. Invoke a method that includes a line that closes the window instance
  3. Invoke a method that includes a line that closes the task to which the window instance belongs
  4. Invoke a method that quits Omnis Studio
  5. On the Windows platform, click the close box for the Omnis Studio window

Some of these are more desirable than others, but they all effectively close a window instance. There may be some conditions where any of these options is undesirable. While it is good practice to not allow the user access to methods that are inappropriate at certain points in the operation of an application, some of these (like quitting Omnis Studio or closing the Omnis Studio instance) might have less obvious solutions than others.

We see here a situation where the programming world and the real world conflict. Here, for the safety of the user, we want to block the path to the exit. The trick is to put in place features that determine when this should be done - and then figure out how to do it. A quick review of the instance closing process could help us see this part of the game board more clearly...

The Instance Closing Process

When we attempt to close an instance, a series of steps is performed by Omnis Studio. These steps differ somewhat in the initial stages depending on the way in which the instance closing is invoked, but here are those steps:

If the user clicks the close box on the title bar of the window, the evClosebox event is triggered. This event is sent to the $event() method of the window instance, not to the $control() method, since the event is performed directly on the window itself. If this event is allowed to proceed, the evClose event is then triggered as a result.

If the user invokes a method that includes a line to close the instance, the evClose event is triggered. If the method line is Close window instance <instance name>, evClose is our only means of detecting the close action. This is also true for method lines that close the parent task of the instance or that close Omnis Studio itself. But if the method line is Do $cinst.$close() or some equivalent, we have another option (explained further on in this article).

In either of these scenarios, the closing process includes a call to a built-in method named $canclose(). This method's purpose is to determine whether conditions are right for closing the instance. If it returns a value of kTrue, the instance can close, but if the method returns a value of kFalse, the instance cannot close at this time (without some overriding action). By default, conditions are always right for closing the instance (that is, the method returns a value of kTrue by default), but the creators of Omnis Studio have given us the means to intervene here too, as we shall soon see.

Assuming that the instance is allowed to close, execution returns to the close method and the internal destruction method is then called. We are given the opportunity to perform additional actions as part of this process by building our own $destruct method.

So the steps are: close box, close instance, test canclose, return to close method, complete destruction. At every point of the way, we have at least one means of intervention with our own code - and in all except the destruction phase, we can prevent the closing process from continuing if our code detects a condition that warrants this.

Detecting evClosebox and evClose Events

This is probably familiar territory for nearly everyone reading this article, so we won't spend much time here. Rather, we will build upon it, introducing other techniques.

Clicking the close box sends the evClosebox message to the $event() method of the window instance. Closing the window, or allowing the evClosebox method to proceed, sends the evClose message to the $event() method of the window instance. In either of these cases, we need to test some state to determine whether the window should be allowed to close. For completely modal applications, #EDATA might be used as an indicator.

But it is more likely that we would create an instance variable (ivBusy, perhaps) that would indicate the window should remain open. We would set this to kTrue at the beginning of any critical process and clear it to kFalse when that process is complete. (Or we would simply set it reversibly at the beginning of any linear process - one that begins and ends within the same method.) We could then test ivBusy in either the evClosebox or evClose blocks within $event() at the window class level to determine whether to allow the action to proceed. Once we have set up such a resource - and the methods to properly populate it - there are some other places where we can examine and react to it within the closing process...

The $close() Method

If we use the $close() method of our instance to close it, we have an additional option for managing the closing process: we can override the $close() method with one of our own devising. Creating a custom $close() method gives us another place from which to monitor and manage this step of the closing process - as long as we close our window instance notationally. That is, the custom $close() method of our window will only be invoked if we execute

<notationtowindowinstance>.$close()

But this may give us certain advantages over using the $event() technique in some circumstances. This is because we can have code that can execute both before and after the $canclose() test in a single method. Here is how this works:

Since our custom $close() method is truly overriding the built-in $close() method, it must include a Do default method line in order for the closing process to actually take place. The Do default command can be positioned anywhere within this method, so we can have code that executes before or after the actually closing process. In fact, if we put code after the Do default line, we will see that this code executes after the $destruct() method (detailed below) as well. Truly the last word in closing the instance!

So there are three distinct parts to a custom $close() method: the test phase, the Do default line and the follow up phase. The test phase is one place where we can determine whether we want the process to go any further. That is, we can test to see whether conditions are right for closing the instance. Of course, this is also the job of the $canclose() method - and $canclose() is invoked for any attempt to close the instance - but there may be cases where we prefer to perform the testing operation at this point. This is also an opportunity to perform operations in preparation for the closing, in the case where we know the $canclose() method will not be conditional.

When the Do default line is executed, control transfers first to the built-in $close() method, then to the $canclose() method (custom if we have provided one or built-in otherwise) and finally to the $destruct() method. These are detailed below.

The follow up phase is an interesting one. At first, one might think that it shouldn't even exist, since the instance that once contained it no longer does. But it does exist - and it can have its uses. We explore this phase in the section titled "The Cheshire Cat's Smile " below. Before pursuing this further, we should examine the other players...

The $canclose() Method

$canclose() is another built-in method of an instance in Omnis Studio. While the need to override $close() might be debatable, $canclose() is built to be overridden! It allows closing to occur carte blanche unless we do override it. In fact, one might wonder why this important method is not included in the class methods for a window automatically, as is $construct(), since it seems to do "nothing" unless given some custom code. There is a good reason for this.

$construct() is called somewhat like a subroutine of the construction process. It is invoked by Omnis Studio at a specific point in the initial building of a window (or other) instance where we are given the opportunity to add commands to that process. We are not "overriding" $construct(), but supplementing it. It does not require a Do default line in order to carry out the basic construction process if we have nothing to add. (This same logic applies to $destruct(), as we shall see below.)

On the other hand, the built-in $canclose() method is being overridden by our custom version. If we create a custom $canclose() method and leave it empty, we will never be able to close our window instance because the method will not return the necessary kTrue value without some code. At the bare minimum, a custom $canclose() method requires one of these two method lines:

Do default

or

Quit method kTrue

I suspect this is, at least in part, why $canclose() is not included in the default class methods as is $construct(). If you decide to modify your default window class in the Component Library to include a custom $canclose() method, bear this in mind and supply at least one of the above lines of code in that method.

The purpose of a custom $canclose() method is to determine whether it is safe, wise, prudent or otherwise sensible to allow the instance to close. While that is its ultimate purpose, we can still perform other operations within this method as long as we return a value of kTrue if our code determines that the instance closing should go forward.

Preventing the closure is simple - just execute the Quit method command, either with no return value or with a return value of kFalse. As long as kTrue is not returned, the instance will not be closed. This has the same effect as executing Quit event handler (Discard event) from within an On evClose block in the $event() method of a window instance, but notice that this is the first mention of "window" I have made here. The $canclose() method can be used more broadly - in a menu instance, for example - in places where event handling just doesn't apply. If the assumption I've made here is correct (and I haven't rigorously tested it), we should be able to use $canclose() in any instantiable class.

If closure is not allowed, there is no warning automatically given to the user. If we want to indicate this state to the user, we must do so with our own code. There is one exception to this: the case where the attempt to close the instance is a byproduct of an attempt to close Omnis Studio and that attempt is blocked by an instance that refuses to be closed. In this case, the user is presented with a dialog offering an option to force Omnis Studio to quit:

But this dialog appears only if the user is using a development copy of Omnis Studio. No dialog appears under these circumstances when using a runtime copy, so be extra careful to provide a means for all instances in your applications to be closed.

If closure is allowed, the next method invoked in the closing process is the $destruct() method of the instance.

The Eve of Destruction

Once the $destruct() method is invoked, there is no turning back. We cannot stop the destruction of the instance from here. The instance has been cleared to close and we are now at the point of performing cleanup and archiving operations in preparation for that closure. $destruct() is the designated janitorial method of the closing process. It is called automatically as part of the instance destruction process, just before the instance is destroyed, so that we might perform any last minute salvage operations and otherwise clean up any messes related to that instance that we know will not be automatically handled by Omnis Studio itself.

There are any number of operations we might perform within $destruct(). What we do here depends on the features we have provided in our application. We may need to do absolutely nothing or we may have developed some very specialized components that need tending. For example, we may offer the facility to remember the last used location, size and configuration for a window instance. If so, this is the point at which we would store that information for later retrieval. Or we may have set up a temporary file outside of Omnis Studio for some sort of caching. This is the point at which we would close and/or destroy that file. Omnis Studio will release the memory locations required by instance variables and other features of the instance, but we are responsible for cleaning up any other constructs we have created for special purposes.

Any instantiable class type, except a table class or an object class, can contain a $destruct() method. While we have used the window class type as our example, menu, toolbar, report, task, remote form and remote task classes can also contain this method. Again, $destruct() does not override any built-in method, but supplements the instance destruction process, so it is perfectly valid for it to not contain any code if there is nothing to clean up for a specific instance.

Normally, the destruction of the instance that follows the execution of the $destruct() method would be the end of this process. But if we began the process by invoking a custom $close() method, there is still some territory left to explore. Just because the instance no longer exists doesn't mean it isn't still working...

The Cheshire Cat's Smile

Because of a concession to certain favored Omnis 7 programming techniques, any method that is executing when the instance that contains it is destroyed continues to execute until it reaches an exit point. In the case of our discussion here, a custom $close() method continues to execute after the Do default line has done its job. The instance itself no longer exists, but any methods still on the method stack will continue to execute.

Interestingly, methods can still be called at this point, but only as private methods. That is, if the class contains a method named $submethod, we can invoke it by using:

Do method $submethod

but not by using

Do $cinst.$submethod()

After the closing operation has taken place, the instance no longer exists, so $cinst is empty. We cannot access properties of the instance, nor can we invoke its public methods using Notation. But its collection of methods appears to linger on until all method execution is finished.

If the instance no longer exists, what could we possibly want to do at this point in the execution of one of its methods? As hinted at earlier, a favorite technique in Omnis 7 was to close one window and then open another as a result, perhaps even launching a procedure in the new window. For example, we may have located a Customer record on the Customer Information window and now we click a pushbutton (or otherwise invoke a procedure) that closes this window, opens the Invoice Entry window and launches the process of inserting a new invoice. In very early versions of Omnis Studio, this technique would fail because the method needed to open the new window would vanish with the closing instance.

So the creators of Omnis Studio chose to allow the class methods of a closing instance to linger on as long as a method continues to execute. This allows us to use the remainder of the custom $close() method as a dispatcher to launch other instances, among other things. In the case given above, we might include a parameter pLaunchInvoice with our $close() method so we could then perform the following:

; preparatory commands
Do default
If pLaunchInvoice
  Open window instance invoiceEntry/CEN
  Do $iwindows.invoiceEntry.$begininvoice()
End if

Your use of this facility is limited only by your imagination...

Summary of Closing an Instance Using a Custom $close() Method

Here is a quick review listing of the stages of closing an instance by calling a custom $close() method:

  1. $close() is invoked and executes code to either test whether to proceed or to prepare for closing the instance
  2. Do default begins the actual closing process
  3. $canclose() is called to test whether the closing process should proceed (we'll assume it returns kTrue here)
  4. $destruct() is called to perform any last minute storage of information regarding the instance and to clean up any residue that Omnis Studio will not automatically handle
  5. The instance is destroyed and no longer exists
  6. $close() completes execution (which may only be an empty line after Do default), allowing us to perform other cleanup duties (that do not require information from the instance) or to perform dispatching operations

This summary is for instances contained within a task. What about closing the task instance itself?

Closing A Task Instance

There is an additional wrinkle to the closing process in the case of a task instance. When we attempt to close a task, Omnis Studio sends the $canclose() message to all instances (windows, menus, etc.) contained within that task, since they too must be closed. In fact, those contained instances must be closed before the task instance can be closed. The result is an all-or-nothing process. If even one instance within the task does not pass the $canclose() test, none of the contained instances is closed and the task continues on. This protects any process that might be ongoing somewhere within the task.

If all instances within the task pass the $canclose() test, that same test is performed on the task itself. If the task also passes that test, the $destruct() method of the task is invoked. This occurs before the $destruct() method of any contained instance is called - and certainly before any of those instances is destroyed. In this way, the $destruct() method of the task can be used to archive any information about the configuration of instances contained within it. There is no stopping the destruction that is to follow, but a proper record of the saved of every detail just prior to this destruction, if that is our wish. (Kind of sounds like a time loop episode of Star Trek,doesn't it?)

So the steps in closing a task instance using a custom $close() method are:

  1. $close() of the task is invoked and executes code to either test whether to proceed or to prepare for closing the task instance
  2. Do default begins the actual closing process
  3. $canclose() is called for each contained instance to test whether the closing process should proceed (we'll assume it returns kTrue for all instances here)
  4. $canclose() of the task is called to test whether the closing process should proceed (we'll assume it returns kTrue here)
  5. $destruct() of the task is called to perform any last minute storage of information regarding the task instance and all of its contained instances and to clean up any residue that Omnis Studio will not automatically handle
  6. $destruct() is called in each contained instance to perform any last minute storage of information regarding that instance and to clean up any residue that Omnis Studio will not automatically handle
  7. Each contained instance is destroyed and no longer exists
  8. The task instance is destroyed and no longer exists
  9. $close() of the task completes execution (which may only be an empty line after Do default), allowing us to perform other cleanup duties (that do not require information from the instance) or to perform dispatching operations

Of course, there is one more level of containment in an Omnis Studio application...

Closing Omnis Studio

What is true for a task as a container of instances also holds true for the instance of Omnis Studio itself as a container of task instances. That is, Omnis Studio will not close (assuming that the computer continues to run) so long as any task (or any instance within any task) returns a kFalse value from the $canclose() test. While this is not insurance against power outages, the unplugging of power cords or the ejection of laptop batteries, it gives us more control over how and when the user can close Omnis Studio. This is especially useful on the Windows platform where the user is empowered by the operating system to simply close the Omnis Studio window - which event we cannot directly detect to effectively quit the program. We can use the features mentioned in this article to avoid that problem.

One way to prevent the user from quitting Omnis Studio in that way is to create a global variable (call it a property of the application if you like) that is used by the $canclose() method of each task instance within the application (or only by the one in the startup task if you prefer) and set it to a value of kFalse by default. Only allow this variable's value to be changed to kTrue by the method you designate for quitting the application - and then only when the proper conditions are met. If the user clicks that pesky close box on the Omnis Studio window, nothing will effectively happen. If even one task cannot be closed, no tasks will be closed and Omnis Studio will continue to run.

Next Time...

This ends our coverage of tasks for now. I hope you have found this exploration to be useful in your work. In the next issue of Omnis Tech News, we'll examine an aspect of Omnis Studio that's a little more visually interesting.

 
© 2005 Copyright of the text and images herein remains with the respective author. 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 the author or Raining Data.
Omnis® and Omnis Studio® are registered trademarks, and Omnis 7™ is a trademark of Raining Data UK Ltd. Other products mentioned are trademarks or registered trademarks of their corporations. All rights reserved.