Tech News Back Issues Issue: 052605
Multiple Task Processes
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:
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
The notational technique is also pretty standard. We use the $open() method against the notation for the task class. An example could be:
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
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:
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:
In this way, we don't permanently change $instancetask, but only use it locally for this special purpose.
You notice that we haven't seen any illustrations or long code listings here. We're dealing with a non-visual aspect of Omnis Studio, so there isn't much to illustrate. And putting in any meaningful code listings would consume many pages of text. So instead, I have placed a small example library on my web site. You can find it in a table on my Demonstration Libraries web page at:
This also contains a few additional features I intend to cover in the next article - specifically, the use of Object reference variables as task variables that access the same object instance. There will be some other items mentioned I'm sure you will also find interesting.
Just So You Know
A couple of weeks ago, I released two open code libraries for sale that were originally intended to become closed library products. Other commitments have kept me from completing them to my own commercial standards and those same commitments are likely to continue for the foreseeable future. But since they could very well be of use to many Omnis Studio developers for enhancing their offerings, I am selling the code on my web site. The two items are my GraphLab library and my Master Calculator library. Further details can be found on my Products web site at:
I hope this article has helped clarify some of the mystery surrounding tasks. There is more to come...
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
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.