Tech News Back Issues Issue: 042805
Task BasicsBy David Swain One of the more challenging features to explain in Omnis Studio is the concept of a task and the way that tasks can be used to advantage. It's not that the concept itself is so difficult - it's that there is such resistance to understanding it. There are many preconceived notions of what individuals think it should be and how it should work, so it is difficult to get across what it actually is and how it actually does work. It seems that programmers - especially we self-employed ones - are a special breed of individualists who on the one hand lust after new technologies and on the other hand resist any change to our established way of doing things. Omnis Studio changed some fundamental things about Omnis programming from the Omnis 7 methodologies we used to use. Tasks are subtle and generally work in the background, so they are easily dismissed as just a different (if clumsier) way of setting up a deployed application (from the one we all got used to from earlier days). But there is much more to tasks than replacing the STARTUP menu with the Startup_Task task. In fact, there is too much for one Tech News article, so we'll start with some of the basics in this one. Let's see if we can make some progress... What Is A Task?There are two ways we can answer this question: philosophically and practically. These are only initial statements to be supported and expanded upon in the rest of this article series: Philosophically, a task is a context within an application. It is a segment, or environment, of a running application that works in a specific way. Consider the example of a program, like Photoshop, that offers a number of different tools from a tool palette. When we select a tool from that palette, a number of features of the application appear to change. The menu bar may acquire or lose menus. Toolbar tools may appear or disappear. Even other palette windows may show, hide or reconfigure themselves. It isn't just the mouse cursor icon that changes! All these changes occur because we have switched context within the application, that is, we have jumped to a different task. Practically, a task is a container of instances in the Omnis Studio runtime environment. All instances of other instantiable class types are spawned inside a task instance - they cannot exist except within a task any more than an entry field can float around without a window. That task instance supplies its "children" with various resources in addition to giving them a place to live. While all instances belong to a task, we can bind some instances even more closely to their task so that they only appear when that task is the current task (to be explained further below). When a task instance is closed, all of the instances it contains are also closed, but there are safeguards that keep this from happening if critical operations are under way somewhere within that task instance. We can also think of a task as a working subdivision of Instance Space - a semi-permeable bubble within which live instances of other class types. These instances can still communicate with instances in other tasks (through messaging), with non-instantiable Omnis Studio resources (by code method calls and other means) and with the world outside Omnis Studio (using specialized method commands or externals) as needed. It is, in short, the way the creators of Omnis Studio decided to set up the runtime environment of Omnis Studio in an object oriented way. Since this is the way Omnis Studio works, it is in our best interest to learn how to take advantage of it. How Does A Task Work?In the beginning, there was Omnis. It breathed life into the startup task and populated it with instances according to the instructions in the $construct method of that task. And it was good. And those instances begat other instances, which were fruitful and multiplied and performed their appointed duties to the end of their days... Or so the legend goes. A task doesn't do much by itself, but it provides a workspace for the windows, menus and reports that we think of as the active elements of our applications. When a task instance is spawned, at least one control-bearing instance must be spawned within it because tasks do not have controls (menu lines, pushbuttons, etc.) of their own. Perhaps part of the problem in understanding tasks is that they are invisible - not even a colored background to let us know they are around. So like the old addage: out of sight, out of mind! The only "handle" on a task that we can give the end user is an active and enabled control of some sort on a GUI instance within that task. So the $construct method of a task must launch at least a window, toolbar or menu that can be used by the operator to open other instances within that task. (Even this is not strictly true, as we will see below, but it is good practice...) These other instances belong to (or are contained within) the task instance to which the method command that opened them belongs. It's like a fraternal organization where new members become part of the same "lodge" or "chapter" as the member who sponsored them. For instances, this is a lifetime membership - they can't quit the organization without dying and they can't change loyalties and switch to a different task. Other instances of the same class can come into being within a different task, but an instance remains a member of the task into which it was born until it is destroyed. A task is the ultimate existential aspect of an Omnis Studio application. It exists strictly in the here-and-now. And it is also egalitarian. It does not care what the origins might be of any of its contained instances. While they exist, they are simply instances within its sphere of influence and able to partake of all of its resources. Instances spawned from classes stored in any open library are welcome. Each task instance is its own little Utopia where the brotherhood of contained instances can drink equally from the trough of task variables and public methods of the task and they are guided and watched over by the task's $control method as their individual needs dictate. All fellow instances within the task exist to work toward the common goals of that task - although these goals can be many and diverse. So a task "works" like any other container. A window contains instance variables that can be used by any method or field contained within that window. A task contains task variables that can be used by any method or field contained within any instance contained within that task instance. A container (GUI instance with component objects or a container field) can contain a $control method that monitors and reacts to events that happen to the objects within it (under the rules guiding the Event Management Cycle - see the Omnis Tech News articles Event Handling: The Basics published 25 September, 2002 and Event Handling: Exploring Events published 16 October, 2002 for more on that subject). Task instances can also contain a $control method for reacting to events that reach the task level of control. Methods in contained instances can also invoke public methods of the task using the Do method command. This may not seem remarkable at first until we remember that, unlike the Do command, the Do method command is scoped and limited to a narrow range of method locations. Public methods of the current task are in scope for the Do method command. Such methods must be named using notation of the form $ctask.$methodname. So if we have a method in our task named $doitinthetask to which we need to pass a parameter value of "testing", we can invoke it from anywhere within any instance running in that task using:
Notice the space before the parameter set in parentheses. This is not notation or messaging we are using here. We only use notation for specifying the method name within the current task. I suppose that private methods of the task cannot be invoked in this way simply because they cannot be named using notation - but this is just a guess... All t his is still a bit general, but there is more to come. First, let's look at a couple of common tasks that we will encounter in every application. The Startup TaskThis is a task of which everyone is at least remotely aware. Most Omnis Studio developers know that when the first library is opened by Omnis Studio, its startup task is also instantiated. By default, every new library is created containing a task class named Startup_Task. The name of this class is also made the default value of the startuptaskname property of that library. For people who object to this name, it can be changed. If a developer prefers the name Alvin or xPfom1m for the startup task in a library, the change is simple to make. Just be sure the value of startuptaskname and the name given to that task class match. The name given to the instance of this class that is automatically launched when the library is opened is the short name of the library, not the name of the task class. This is the name of the library file without the .lbs extension. That is, of course, unless the wise programmer has given a value to the library's defaultname property (found under the Prefs tab in the Property Manager for the library), in which case that value will be used. The defaultname property allows the internal name of the library (and the name of the startup task launched for it) to be the same whether or not the end user decides to rename the library file - so that any notation that relies on this name will continue to work. We can give it any name we wish - it does not have to be the name of the library... and it certainly does not have to be STARTUP or Startup_Task (although no one is stopping you...)... We use the $construct method of the startup task to set up the initial configuration of our applications (install the main menu, replace the File and Edit menus, launch a logon window, show a splash screen, etc.). We can also use the $destruct method of this task to perform cleanup operations (like closing datafiles or logging off SQL sessions) as the application is closed. Beyond that, most developers don't concern themselves with the startup task. When this is the case, we have a single-task application. This does not generally cause any problems. The developer can happily go about developing the application in somewhat an Omnis 7 fashion, treating task variables as global, treating the $control method of the task as a library control procedure and using public methods of that task as custom functions accessed via notation. This is fine and uncomplicated - until that developer decides to use multiple libraries in the application. Then the issue of the design task of each class requires some attention. The Design TaskEvery code-bearing class in Omnis Studio (except task and remote task classes, but including search and code classes) has a property named designtaskname. This property contains the name of the task (or remote task for remote forms) that the class is designed to work within. This is the task whose task variables can be referred to directly and without qualification while working with that class in design mode. By default, new classes are given the name of the startup task as the value of this property - even in multiple task libraries. Of course, this can be changed using either the Property Manager or Notation. In the Property Manager, a convenient dropdown list offers the names of all tasks within the current library. For classes, like Object classes, that can be used within either tasks or remote tasks, this dropdown list includes both task types (task and remote task). If we wish to name some other task (like one from another library, for example) as the design task for a given class, we can simply type or paste its name into the property value field in the Property Manager. If that task class belongs to a different library, its name must be qualified by the name of its library (for example, otherlib.taskname) and that library should be open during development and use of classes that require its resources. The designtaskname value for a class does not dictate that the named task will be opened when this class is instantiated. Each instance of a class belongs to the task instance within which it is launched. The designtaskname value is only an indication of the task within which this class is designed or intended to be used. For single-task, single-library applications, this is not an issue. But if a developer decides to use multiple tasks within an application (or multiple libraries), it is important to keep this in mind. In fact, launching a test instance of a window class (for example), using any of the usual means, will spawn that window instance within the startup task - even if the design task is some other task and that task is instantiated. The instance must be launched from within an instance of the proper task in order to be correctly tested. This is simply an extension of the rule that we must launch a test instance of a class to properly test any of its methods that require instance resources (public methods or instance variables, for example) rather than relying on the Cmnd/Ctrl-E key combination we always used in Omnis 7. Before the mumbling gets too loud, let me point out that this is no different from any other kind of engineering. If we were working with a prototype sports car in our lab in Michigan, we would still have to take it out to the track for most real testing. In fact, if we needed to do the testing for hot and dry conditions in the middle of February, we would have to ship the car to some warm, dry location, let it sit for a couple of days to acclimate and then perform our tests. We have to put our instance into the proper environment in order to test it in this object oriented universe as well. The more complex we make the total environment (adding more libraries and/or tasks), the more work it takes to maneuver our test subject into position for the test. OK, so what would happen if we didn't launch the startup task instance at all? Would our "correct" task automatically instantiate? The Default TaskWe can launch a library in such a way as to suppress the instantiation of its startup task. The Open library command has a Do not open startup task option that does this, but we can also simply hold down the Option/Alt key as we open the library. Whether we remove all task classes from a library or simply suppress the startup task, there is still at least one task running in our development copy of Omnis Studio. This is the default task. It contains the IDE elements of our development version (Browser, etc.) and it serves as the backup testing task when there is no startup task. Since Omnis Studio is built using Omnis Studio, it stands to reason that there would have to be some sort of task instance around. (There are actually quite a few...) Let's try a little experiment. In a library (any one will do), create a new Window class (notice the default value of its designtask property) and put a pushbutton on it. In the $event method of that pushbutton, put the following code:
Now launch a test instance and click on the pushbutton. An OK dialog should appear with the name of the library (or the value you assigned to the library's defaultname property if you assigned one). This is what happens in the default state. Now close the library and then reopen it, but hold down the Option/Alt key when you open it. If the $construct method of your startup task was supposed to perform any actions, notice that these did not occur. But the Browser should have appeared (the startup task for the IDE is not suppressed). Launch a test instance of our Window class and click the pushbutton. The OK dialog should now proclaim "defaulttask". This task is always lurking in the background in the development kit, so if task-related issues come up in your debugging of instances that are supposed to launch into the startup task, it is worth checking to see whether the current task is truly the startup task or you accidentally suppressed it and the default task came to the rescue. Depending on what OS platform your computer uses, what add-on tools you might have open and even which version of Omnis Studio you are running, there are a number of other task instances that may be open as well. We won't poke around n them here, but the advanced Omnis Studio developer may find them interesting. Basic Task PropertiesA task class really doesn't have any distinguishing properties beyond the general properties for a class. These include name, desc, moddate, userinfo, etc. The existence of the superclass and issupercomponent properties indicates that we can use inheritance with tasks (a useful thing to know!). Other than that, there is nothing remarkable about task class properties. But these are only properties of a task class. A task instance on the other hand has some important and unique properties. These help us to work with tasks more effectively. I hesitate to bring this up so early in the discussion, but here we go... When I wrote earlier that instances launched from a method running in a task belong to that task, I lied - but only slightly. That is certainly the default behavior and what will happen under normal circumstances. But we can use the $instancetask property of a task instance to change this. This property holds the name of (actually, a reference to) the task instance into which new instances will be spawned from the current task. Its value defaults to the task to which the property belongs, but we can give it any value we want. (Of course, this is most productive if we give point it at an existing task instance...) While this property is of Simple character data type, it holds a notational reference to the target task. So to assign a value to it, we must use one of these two forms for the assignment:
The advantage of the Calculate technique is that it is reversible, so we can assign it (reversibly) as we need to use it and know that it will default to its original state when our method completes execution:
So from our main menu (for example), which may be running in our startup task, we can launch other windows directly into their appropriate task instances (assuming that we have already launched those tasks, of course). Very handy! But we're getting a little ahead of ourselves with all this multiple task talk! Hold those thoughts until next month... Another task instance property is $autoactivate. This takes a Boolean value that indicates whether the task is automatically activated when one of its windows is made the top window (by either an executing method or a user action). This property defaults to a value of kTrue, but we can change it if we need to. More on what it means for a task to be "activated", as well as how we might use this property to advantage, in the next article (another multiple task issue). An interesting task instance property is $isIDE. This property also holds a Boolean value, but this one defaults to kFalse. If this value is set to kTrue, instances launched by the task are launched into the task that manages the IDE. Why would we want to do this? Remember that Omnis Studio is itself written in Omnis Studio. Remember also that we can build add-on tools to be used with a development copy of Omnis Studio. We could even write an alternate IDE if we really wanted to (and thought there was enough of a market for it to spend the time!). That is where this property would be used. Built-In MethodsThere are two built-in methods of a task instance: $close and $canclose. These two methods work together to manage the process of closing a task instance. As you might suspect, the $close method is used to close a task instance, just like the method of the same name for any other class. And just like any other type of instance, the $canclose method is used to test whether closing this instance is currently allowed, with a return value of kTrue meaning that it is OK to close the instance. But for task instances, its own $canclose method is not the only one queried. The $canclose method of all instances within the task are queried as well. If any of these report a kFalse result, nothing is closed. We may very well want to override the $canclose method with our own custom version to have even more control over this process. But this, too, will have to wait for another article... Next TimeThe topic of tasks is a big one, so we just can't fit it into one article and do each piece justice. I hope we have done more to enlighten than to confuse in this first article. In the next article we will examine properties, methods and processes that apply to the use of multiple tasks in an application.
|
|
© 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
Omnis Software. Omnis® and Omnis Studio® are registered trademarks, and Omnis 7 is a trademark of Omnis Software UK Ltd. Other products mentioned are trademarks or registered trademarks of their corporations. All rights reserved. |