Closing Instances and
Tasks
By David Swain
Polymath Business Systems
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:
- Click the close box on the title bar of the window (or perform
the keyboard equivalent Command/Control-W)
- Invoke a method that includes a line that closes the window
instance
- Invoke a method that includes a line that closes the task to
which the window instance belongs
- Invoke a method that quits Omnis Studio
- 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:
- $close() is invoked and executes code to either test
whether to proceed or to prepare for closing the instance
- Do default begins the actual closing process
- $canclose() is called to test whether the closing process
should proceed (we'll assume it returns kTrue here)
- $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
- The instance is destroyed and no longer exists
- $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:
- $close() of the task is invoked and executes code to
either test whether to proceed or to prepare for closing the task
instance
- Do default begins the actual closing process
- $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)
- $canclose() of the task is called to test whether the
closing process should proceed (we'll assume it returns kTrue
here)
- $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
- $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
- Each contained instance is destroyed and no longer exists
- The task instance is destroyed and no longer exists
- $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.
|