Multiple Task Processes
By David Swain
Polymath Business Systems
In the last issue we introduced
a number of concepts and terms regarding tasks. This time we'll
attempt to do something constructive with them after delving just
a bit deeper. Let's begin by examining the realm of event management
from the task point of view.
Task Level Event Management
Tasks are containers of instances and GUI instances (the only ones
for which event management has meaning) are containers of component
object (some of which can also contain other component objects).
Here is a simplified version of the event handling process:
When an event is performed on some item, that item's $event
method (if one exists) is invoked to see whether any instructions
have been left on how to handle this specific event. If not, the
$control method (if one exists) of the most immediate container
for that object is invoked and the same test is performed. This
continues until an event handling method in this direct chain of
command is found that can handle the current event or until
the topmost level of containment is reached without finding the
necessary handler. The topmost level of containment is the task
level. Nothing (other than Omnis itself - which does not directly
contain any user-defined methods) can contain a task. So a task
can be given a $control method to handle events that happen
to objects it contains.
But while a task is the top level of the event handling cycle and
it can handle events that occur to instances and components contained
within itself, there are no events that can happen directly to
a task instance. This is a direct result of there being no graphical
user interface for a task, so it cannot receive events like clicks
or keystrokes. A task never has the focus in the way that an object
on a window instance does, so it can never become the current object
($cobj).
For this reason, it is completely futile to give a task class a
$event method. Without invoking this method explicitly,
it would never be called. While an On command would appear
to offer a wide range of event choices when used in a task method
named "$event" (if we were to create one), on closer examination
we would realize that none of those choices actually applies to
a task instance.
But this is not the end of the story...
While there are no GUI events that can occur directly to a task,
there are changes in status that can occur to a task instance
in a multi-task application. These have the feel of events,
but they are handled in a different way. But before we can explore
how we handle these changes of context, we first must understand
the states involved.
The Active Task
The active task is the task whose GUI presence is most
prominent. Usually this means the task to which the topmost window
instance belongs, but if there are no window instances currently
open, then other items will make that determination. So the active
task is the current context for potential events in the
application. That is, the $control method for the active task is
the one that has ultimate control in the event management cycle
- if events ever get passed up to that level.
In the default case, if the user brings a window to the front that
belongs to a different task, that task then becomes the active task.
I say "in the default case" because we can switch this
behavior off and take more direct control with our own code. This
is the way it works if the $autoactivate property of the
task is set to a value of kTrue (the default value for
that property), but we can switch it to a value of kFalse
if we wish. But how would we then make a given task the active task?
The root level of Omnis Studio has a property named $activetask.
This contains a reference to the task that is the active task -
and it is a read/write variable. Interestingly, it appears
to be a Character rather than an Item reference
variable, so we set it using the Calculate command rather
than the Set reference command. But to query it, we must
resolve it and then notationally request a property: $activetask().$name,
for example. We can set a given task as the active task by executing
the command line:
Calculate $root.$activetask as $itasks.<nameoftaskinstance>
So what is the point of doing this? Well, we can bind
some of the GUI instances contained by a task more closely to it
so that they are only visible when that task is active. Each instance
we spawn within a task contains a property named $local.
This property is of Boolean data type and is given a value
of kFalse by default. This means that the initial
state of any instance opened within a task is to not
be local. But if we set this property's value to kTrue
for a given instance (a menu instance perhaps), that item will only
appear when its task is the active task - and it will disappear
when some other task is activated. This is done automatically,
so we don't have to write code to do it for every possible change
of context.
So like in the case of Photoshop I cited in the last article, we
can make menus, toolbars, palette windows and other user aids appear
in the porper context and disappear when they are not appropriate
using this facility. The designation of the active task,
then, determines the visibility of certain items within
a task and also determines which event handler is in control
at the task level.
The Current Task
The current task is the task that contains the currently
executing method. This setting determines the accessibility
of task variables and other resources at the task level. A reference
to the current task is held in the root property $ctask,
with which most of us are familiar. This property variable,
by the way, is read only. In many cases, the current task
will also be the active task, so why do we bother making the distinction?
It is quite possible for the current task to not be the
active task. Here is how: Instances themselves understand to which
task instance they belong. Besides querying $ctask().$name,
we can also query $cinst.$task().$name or even <instancegroup>.<instancename>.$task().$name
from outside the instance. But the "current instances"
groups ($iwindows, $imenus, etc.) are blind to
the task instances within which their members live, so instances
can send messages to each other across task boundaries by simply
addressing the target instance through the appropriate group ($iwindows.<windowinstancename>.<publicmethodname>,
for example). When a method in a different task is invoked and begins
execution, the current task pointer ($ctask, which determines
which task variables and private methods are in scope) changes,
but the active task (which determines the visibility of instances
local to a task) does not. This is an important distinction to master
when dealing with multiple-task applications.
And there are other processes that take place below the surface
when either the current or the active task context changes...
Task Status Change Methods
When the $activetask or $ctask pointer changes,
certain messages are sent to the tasks involved in that status change.
We can add our own code to this process by creating custom methods
with specific names within our tasks. These methods augment
the processes involved (like when we create custom $construct
or $destruct methods) rather than overriding them
(like when we create a custom $redraw or $print
method), so it is not necessary or appropriate to include a Do
default command line within these methods.
When the active task changes, the previous active
task is sent a $deactivate message and then the new
active task is sent a $activate message. We can create
methods with these names in any of our task classes to react to
this change of context. Since this kind of context switch deals
with visibility of GUI items, we are most likely to perform actions
along those lines in these methods. For example, we might open or
close specific windows (if they are not already shown or hidden
by virtue of being local to their task). Or we might redraw certain
elements on specific windows or perform other actions to synchronize
their contents with the rest of the application. There are different
needs for every situation, but we can take control of this process
using these methods.
When a method is invoked in an instance residing in a different
task (or in the target task itself), the task containing the method
making the call is sent a $suspend message. Depending on
whether the task for the target method must be instantiated or already
exists, that task is sent either a $construct or a $resume
method. The opposite process occurs when execution returns to the
calling method. (That is, the task containing the calling method
whose return point is about to continue execution receives a $resume
message and the task containing the called method that has now completed
execution is sent a $suspend message.) Since the circumstances
that can trigger these methods do not involve GUI issues, these
methods should not change visible items. So we should not open or
close windows or other instances, change anything about existing
instances or perform yet another switch of execution context within
them. Of course, we can certainly do any of those things in the
method whose execution triggers these status change methods,
just not in the status change methods themselves.
Task-Related Properties of Instances
We have already been introduced to the $local property
of an instance within a task and we learned that it controls the
visibility of that instance relative to whether its task
is the active task. There is another property of an instance launched
within a task that controls the accessibility of its methods as
well. This is the $isprivate property of the instance.
This property of an instance is set to a value of kFalse
by default, meaning that instances launched within a task are public
by default and are then accessible from outside the task. But we
can set this property to a value of kTrue, which makes
the instance accessible only from within the task that contains
it. This means that methods running in other tasks cannot see or
send messages to private instances.
But even that is not quite accurate. We can allow access
to such a private instance, or to only a specific aspect of it,
by passing a reference to the item we wish to make public to an
Item reference variable residing outside the task. This
allows methods to tunnel through the privacy firewall, but limits
accessibility to this item to the scope of the Item reference
variable the grants access. This should have some appeal to hardcore
OO programmers! When an instance is made private to a task, it only
appears in its appropriate instances group when its task is the
current task. So even if we know it exists and what its
name must be, the $ixxx notational group denies its existence
unless the querying method is within scope, which means within the
same task as the private instance.
An additional twist of which we must remain aware is that when
an instance is made local to a task, it is automatically
also made private to that task. so the $local
property value, when set to kTrue, overrides the $isprivate
property value. $isprivate can only be set independently
when $local is set to kFalse.
A task instance also has a $isprivate property, so we
can make a task instance private to itself. This means that nothing
within that task can be accessed from the outside (unless a notational
refrence is passed to some outside variable). This task can still
be made the active task (as long as we have provided a
visible GUI instance within it), but it cannot be accessed notationally
(not even to close it!) except from some where within its own realm.
That is, it cannot be made the current task from the outside.
Launching A New Task Instance
There are two basic ways we can launch an additional task within
an application. As usual, there is a 4GL technique and a notational
technique.
We can use the Open task instance command, for example.
This command gives us the option of naming the instance it spawns
and of passing parameters to its $construct method. The
syntax for this command is:
Open task instance <classname> [<instancename>]
[(<parameter list>)]
So we must provide the name of the task class, but a name for the
instance is optional. The name of the class will be used (assuming
that an instance ot a task with that name doesn't already exist)
by default. Or instead, we can allow Omnis Studio to provide a unique
instance name by specifying '*' as the instance name.
We can also provide a comma-delimited list of parameters for the
$construct method enclosed in parentheses.
The notational technique is also pretty standard. We use the $open()
method against the notation for the task class. An example could
be:
Do $clib.$tasks.<classname>.$open([instancename],[<parameter
list>])
The parameters for the $open() method are both optional. In fact,
we can we can leave the first parameter completely empty (not even
quotes) while still supplying a set of parameters. Or we can again
supply '*' to force Omnis Studio to come up with a
unique instance name.
As mentioned last time, we should generally use the $construct
method of the task to launch at least one GUI instance within itself...
Launching Instances Within The Current Task Instance
By default, the task that contains the method that launches an
instance (of a window, menu, etc.) is the task that will contain
that instance. So if we perform an Open window instance
command from somewhere within a given task (and that task is in
the usual default state), the window instance will belong to that
task. For any instance that we wish to be local to the task, we
can set the $local property value to kTrue either
in the method that launches the instance or in the $construct
method of the instance itself (if we know ahead of time that we
always want instances of this class to be local to the
task into which they are born). This is generally the way we would
want this to work.
Launching Instances Within A Different Task Instance
But let's consider a fairly common situation: Suppose we have a
main menu of our application that was installed from within the
startup task and belongs to that task, yet we want to launch windows
from there that will be contained in other tasks. This is not
the default behavior, yet it is a perfectly reasonable thing to
want to do.
There is a property of a task that allows for this to happen. It
is the $instancetask property. As with $activetask,
this is a Character variable that holds a notational reference
to a task instance. By default, it points to the instance for which
it is a property, but we can change that just like we do for $activetask.
So if we are executing code in our startup task, but wish to set
$instancetask to point to our customerTask instance,
we would use:
Calculate $ctask.$instancetask as $itasks.customerTask
The nice thing about needing to use the Calculate command
is that it is reversible. (We could have used $assign(),
but then we lose this advantage.) So in a menu method that needs
to launch the customerEntry window instance into the customerTask
task instance (so that it has access to all the resources expected
when it was designed), we could use:
Begin reversible block
Calculate $ctask.$instancetask as $itasks.customerTask
End reversible block
Open window instance customerWindow/CEN
In this way, we don't permanently change $instancetask,
but only use it locally for this special purpose.
|