IT SOLUTIONS
Your full service technology partner! 
-Collapse +Expand
Paradox
Search Paradox Group:

Advanced
-Collapse +Expand Paradox To/From
To/FromCODEGuides
-Collapse +Expand Paradox Store
PRESTWOODSTORE

Prestwood eMagazine

March Edition
Subscribe now! It's Free!
Enter your email:

   ► KBDesktop Data...Paradox & Ob...ObjectPAL Co...OPAL: Langua...   Print This     
  From the May 2015 Issue of Prestwood eMag
 
Paradox OPAL: Language Basics:
Exploring Where To Put Code
 
Posted 11 years ago on 2/22/2008
Take Away:

Think levels when you code in the Pardox ObjectPAL development environment.

KB100868



[Updated 2/22/2008, cleaned up, added images, updated text]

Whether it's Paradox, Visual Basic, or Delphi, one of the toughest tasks for new programmers is deciding where to put code. All of these tools are event driven environments and allow you the programmer to attach code routines to objects that respond to events. This chapter will introduce you to containers, scope, bubbling, and other concepts and techniques that will aid you in placement of code.

Containers

Paradox employs what is called containership, which enables you to put a smaller object inside a larger object. You could say that the smaller object is contained by the larger object. In ObjectPAL, the built-in container variable refers to the object that contains the current object. A container object completely surrounds and controls the behavior of all the objects within it. The rules of a container dictate that when you move a container, its contained objects also move. Likewise, when you delete a container, its contained objects are also deleted.

When you have objects that can contain other objects, the containership, or path, has to stop somewhere. In ObjectPAL, the form is the highest level of container. A form contains at least one page, and the page contains design objects such as fields, buttons, table frames, and bitmaps, shown next. Note that the page contains the larger box, and that the larger box contains the circle, the smaller box, and the button. The field is contained by the small box.

Illustration 1

Remember that the form contains all these objects and that it is the highest level of container in Paradox. This creates special problems for programmers when you have an application that uses many forms. For example, if no two forms have a higher container, then how do you develop a common menu for use with all forms in an application? The Application Framework solves this problem and is discussed in a later chapter.

The keywords and object variables that you use with containers are as follows:

  • disableDefault Stops the built-in behavior.
  • doDefault Executes the built-in behavior now.
  • enableDefault Allows the built-in behavior.
  • passEvent Passes event up to the containership path to the parent container.
  • self Refers to the object the code is on.
  • active Refers to the object with focus.
  • container Refers to the container of the object the code is on.
  • lastMouseClicked Last object to receive a left mouse click.
  • lastMouseRightClicked Last object to receive a right mouse click.
  • subject Refers to another object.

You can refer to a container with the container variable. For example, message(self.container.name) displays the name of the object that contains self.

Containership Hierarchy: What Contains WhatThe Object Tree visually shows you the containership hierarchy of a form. It is one of the most important tools for developing forms and writing ObjectPAL code (see Figure 5-1). The Object Tree shows you what objects contain what other objects.Figure 1: The Object Browser with only the Object Tree showing

Use the Object Tree to rename objects quickly. Also, use it to see what contains what and to see which objects have code on them. Objects with code have a blue dot.

You can attach code to the objects that show up in the Object Tree. You can attach code to as many objects as you want, in any order that you want. Attaching code directly to the object with which you want to work makes ObjectPAL easier to use than many other development environments. For example, you can place a box and a circle on a form and place code in the mouseClick event of both objects. Because the form and the other UIObjects you placed on the form contain built-in events, when you run the form, Paradox takes care of trapping a mouse click when the user clicks on either the box or the circle.

Containership Rules

The following are several rules dealing with containership:

  • Deleting a container deletes all embedded objects.
  • Containership determines dot notation.
  • Containership enables you to have duplicate named objects in the same form.
  • Noise names are not part of the containership path. A noise name is the default name Paradox gives new objects. Noise names always start with #.
  • An object is embedded only if it is completely within the boundaries of the container.
  • Objects inside a container can see the variables, custom methods, and custom procedures of its container (the ancestors all the way up the hierarchy path to the form object). The form object is the base object for UIObjects.
  • A container cannot see the variables, custom methods, and custom procedures of its embedded (or descendant) objects.

Sometimes you may wish to have one object visually inside another object, but not have that object within the containership of it. Every object has a Contain Objects property which, by default, is checked. When checked, all objects within the visual boundary of the object are within the containership of the object. Uncheck the Contain Objects property of an object to break the containership path (the hierarchy path).

Scope: What Can See What

The scope of an object is the range of objects that have access to it. For example, if you declare a variable in the form's Var window, that variable may be used by any object in the form, but not by objects outside the form. Scope has a definite path determined by the containership hierarchy. An object can see all the variables or constants above it and within it, but not below it. In other words, an object can't see the variables, constants, and procedures of the objects it contains. Also, an object can't see variables, constants, or procedures that are on another branch of the Object Tree. Objects that are contained can see their containers' variables, constants, and procedures. That is, they "inherit" them (and can overwrite them with their own, an OOP feature).

For example, in Figure 5-2, the inner box is contained by the outer box. The inner box can see all its own variables and all the variables of its container (the outer box) as well as all the ancestor container objects, in this case, the page and form. The outer box, on the other hand, can see its own variables as well as the page and form variables. It can't see the variables of the inner box.

Figure 2: Scope is determined by containership hierarchy

Scope can be summed up by the phrase "what can see what." A noise name is the default name that Paradox gives an object; noise names always start with a "#". Noise names don't interfere with scope, but real names do. Understanding this difference is absolutely crucial to understanding scope. When you view the containership hierarchy, you can find out which objects see which objects. You need to know where and when you can use duplicate names of objects on forms. The next example explores duplicate object names and duplicate variables.

ObjectPAL Is Event Driven

How does Paradox execute all these independent routines? ObjectPAL is an event-driven language, much like Delphi, Visual Basic, C++Builder, and JBuilder. You place UIObjects that have events already attached to them on to a form. The event handler monitors what events occur as the user interfaces with the running application and automatically executes the code attached to the event (both yours and the built-in default code). This mechanism of triggering events is known as an event handler. The plan or guideline that the event handler uses to trigger events is known as the event model.

To understand ObjectPAL, you first must understand the event model. Nothing else will make sense until you understand it. For now, keep the following points in mind:

  • Every UIObject has a set of events already associated with it.
  • All events are sent messages in the form of a variable.
  • The variable that is passed from event to event is always named eventInfo.
  • Although all messages passed to events are named eventInfo, the messages themselves come in several different types (for example: ActionEvent, MouseEvent, and MenuEvent). You can see what type of message is sent to any particular event within the event itself. For example, right after the pushButton event name is (var eventInfo Event). This means a message named eventInfo of type Event is passed to the pushButton event.
  • Every message goes to the form object's event first. With the form prefilter, you can intercept an event before the target gets it. If you don't place code in the form's prefilter, the event is not affected by the prefilter. The form serves as a kind of dispatcher. An event goes to the form, and then the form sends the event packet to the target object.
  • Every message has a target. This target is established in various ways. For example, when you click a field, it becomes the target of the event. Within the ObjectPAL editor for any event, you can find the target by inspecting the eventInfo variable with eventInfo.getTarget(ui). The current target is then assigned to the variable ui (you must declare ui as a UIObject variable type). The form sends the event to the target. Internal events stop after the target. Internal events such as open, close, and arrive go to the form and then to the target, and then they terminate.
  • There are three types of events: external, internal, and special.
  • Only external events bubble. The full path of an external event goes to the form's prefilter, then to the target object, and then it bubbles (is passed back up) the containership hierarchy. If an external event such as mouseDown, keyPhysical, and error are not trapped for at the form level or the target level--in other words, no built-in code responds to the event--the object passes the event to its container. If the container has no built-in code that traps for that external event, it is passed to its container and so on until it reaches the form for the second time, where it stops.
  • The form can see external events twice. If an external event has no built-in code that traps for it, the form sees it twice. The prefilter equals False when the form is the target of the event. The prefilter also equals False when another object is the target of the event and the form is seeing it for the second time.

Bubbling: The Path of Events

Bubbling of external events is a special topic that deserves more attention. An event has one of two definite paths: either form and then target, or form, target, and bubble back up to the form. Bubbling is when an event moves back up the containership path to the form. An external event such as mouseDown goes to the form, then to the target, and then it bubbles up the containership hierarchy until it reaches the form a second time, where it stops. The event might go through the entire path. If any of the objects in the path have built-in code that uses the event, the event might get cut short. (Your code does not stop bubbling; only built-in code does.) For example, mouseDown normally bubbles up to the form. If you left-click a field, however, the field knows what to do with mouseDown and the mouseDown event is not bubbled.

Most of the time, your code executes on the target. The target object executes an event such as mouseDown. The event goes first to the form. If the form doesn't have the appropriate code on the correct method, the event is returned to the target for execution. If the target doesn't have the appropriate code, the code is bubbled up through the containership path to the form, where it stops. The form sees the code twice, at the start and at the end, unless another object, such as the target's container, intercepts the event with built-in code, such as the preceding field example.

Now, follow what happens to a button's pushButton event when a user clicks it. The target object (the button) sends the pushButton event to the form. Because the form doesn't use a pushButton event, the event is returned to the target for execution. If the target (the button) doesn't have the appropriate built-in code, the event is bubbled up through the containership path to the form, where it stops. The form can see an event twice, at the start and at the end of its life cycle. You can place code anywhere on the containership path to trap for the event. In fact, you can place it several times on the path and all the code will execute.

Suppose that you want to trap for an event whenever the user arrives on a record. At the form's prefilter, you can trap for DataArriveRecord, as in the following:

1: method action(var eventInfo ActionEvent)  
2: if eventInfo.isPreFilter()  
3: then  
4: ;This code executes for each object on the form  
5: if eventInfo.id() = DataArriveRecord then  
6: beep() ;Do your stuff here  
7: endIf  
8: else  
9: ;This code executes only for the form  
10: endIf  11: endMethod

If you are using a table frame, which has a record object, you can't move lines 5, 6, and 7 to the else part of the if structure because the record object of the table frame will use up the event and not allow it to bubble up to the else clause of the form. The action DataArriveRecord is never bubbled up to the form.

The event you're interested in applies to a table frame--one of the many other objects that are caught by the prefilter test. The comment after the then part of the if structure reads, This code executes for each object on the form. This refers to all the objects on the form except the form itself, which is tested in the else clause.

If you create a single record form--that is, a form with just fields on it--you can use the preceding else clause to trap for DataArriveRecord because it bubbles back up to the form.

Containership, Scope, and Bubbling Are Intertwined

The containership hierarchy is the branching tree represented in the Object Tree. The Object Tree enables you to see the containership hierarchy, or path, of objects. Containership hierarchy is closely related to bubbling and scope. Events go to the form and then to the target. Then, the event bubbles up the containership hierarchy. The event packet path is determined by the containership hierarchy. Scope is what can see what. Use the Object Tree to determine the scope of objects. If an object on the containership path has been renamed, then it has defined part of the scope path. If the object has not been renamed, then it is not part of the scope path.

Study the Events in Combination

An excellent technique for placing code on an object is to place code in "before and after" combinations. Many events have counterparts: for example, open and close, arrive and depart, canArrive and canDepart, and mouseEnter and mouseExit. If you need to code in a "before and after" maneuver, think and code in one of these method combinations. For example, occasionally you might need to create a table when a form is opened and delete it when the form is exited. The open and close combination is a good place to do this. open and canDepart might be even better, but that's up to you as the programmer.

mouseEnter and mouseExit

Whenever you want something to occur when the pointer is within the boundaries of an object, use the mouseEnter and mouseExit combination.

Suppose that you want to display a message on the status bar whenever the mouse pointer is over a button. To do this, use the message() procedure in the mouseEnter and mouseExit events.

Step By Step
    1. Create a new form with a button on it. Change the label of the button to Close, as shown here:

      Illustration 2

    2. Add commands to the button. In this case, close the form by adding line 3 to the pushButton event. Line 3 closes the current form. Note that close() is used as a procedure in this case--it knows what object to work with.

      1: ;M-ENTER :: Button :: pushButton

      2: method pushButton(var eventInfo Event)

      3: close()

      4: endMethod



    3. Place the message on the status bar by adding line 3 to the mouseEnter event of the button. Line 3, the mouseEnter event sends a message to the status bar whenever the mouse pointer enters the boundaries of the button.

      1: ;M-ENTER ::Button :: mouseEnter

      2: method mouseEnter(var eventInfo MouseEvent)

      3: message("This button closes the form")

      4: endMethod



    4. Remove the message from the status bar by adding line 3 to the mouseExit event of the button. Line 3 posts a new message, the Null character.

      1: ; M-ENTER ::Button :: mouseExit

      2: method mouseExit(var eventInfo MouseEvent)

      3: message("")

      4: endMethod



    5. Check the syntax, save the form as M-ENTER.FSL, and run it. Move your cursor over the button and keep an eye on the status bar (see Figure 5-3).

Figure 3: Completed mouseEnter/mouseExit example

Rather than use the mouseExit method of every object, you can put code in the prefilter of the form's mouseExit method. Remember that external commands always bubble up. Therefore, putting message("") at the form level clears the status bar whenever the mouse leaves an object. Doing this will enable you to just add a message() procedure to the mouseEnter of all the objects you wish to provide this type of help for.

open Versus arrive

An object is always opened first, then arrived on. Therefore, open occurs before arrive. Remember that the default behavior happens last. When you open an object, the code on the form's built-in open method executes, and the form is opened. If you put code that deals with a UIObject in the open method, the code might not execute correctly because the objects don't exist yet. You could put doDefault before your code to execute the default behavior, or you could move your code to the arrive event.

The open event is a good place to initialize a variable, create a table, and deal with the form--for example, to maximize or resize it. The arrive event is a good place to set the properties of the objects on the form--for example, the values of the field objects.

newValue Versus changeValue

The newValue event occurs whenever the value for an edit region changes value onscreen. This occurs whether or not the field is defined. The changeValue event occurs with both defined and undefined fields, but behaves quite differently for each. The changeValue event on an undefined field occurs whenever the value in it changes. With defined fields, however, it occurs only after a field is read from a table and changed by the user.

The changeValue event is a good place to do something based on a change in the table value. For example, you could perform some operation when payment type changes from cash to credit. If you want something to happen whenever a user changes a value in a field--undefined or defined--use changeValue.

keyChar Versus keyPhysical

The keyChar and keyPhysical events trap for the user pressing keys on the keyboard. The keyChar event traps for only the character keyboard keys; that is, the keys that print characters to the screen. keyPhysical traps for all the keyboard keys, both character keys and physical keys. The keyPhysical event filters the keyboard first and passes printable characters to the screen.

Use the keyChar event to trap for character keys, such as "A", "B", "1", "2", "(", and ";". Use the keyPhysical event to trap for keys such as esc, F1, F8, and tab. If you need to trap for both physical and character keys in a routine, use only keyPhysical because it can trap for both.

action, menuAction, and error

Use the action event to trap for table actions such as DataInsertRecord and DataPostRecord. Whenever a user does something related to the table, such as inserting a record or deleting a record, an action constant is sent to the action method. Use menuAction to catch user selection of menus, the Toolbar, and the control box. Whenever a user selects a menu option or clicks a button on the Toolbar, a menu constant is sent to the menuAction event of the form. You could use the error event to trap for errors, but because the built-in error event is always called after errors occur, use action instead. A good use for error is to add to the built-in error behavior--for example, to log all errors to a table.
About Coding

When new users to Paradox begin writing code in ObjectPAL, they often write tremendous amounts of code. The amount of code can become overwhelming if you don't understand the fundamentals of the product.

You usually follow certain steps whenever you develop an application. Before writing a single line of ObjectPAL code, you always should build the data model and arrange the form until it's similar to what you want. Then, run the form and work with it to see what it does and doesn't do. When you find something that you want to work differently, ask yourself, "What do I want to happen?" and "When do I want it to happen?" Try to do this for only one task or object at a time. In other words, develop in small steps.

Go back and forth between Design mode and Run mode and test your work incrementally. When you're done coding and testing a single object, move to the next. By refining your application in steps, you end up with functioning code in bite-sized chunks. If you try to tackle in one step all the work that an application requires, you can easily end up frustrated, with messy code. Remember, program and test one task at a time.

Watch Out for Spaghetti Code

BASIC, early Pascal, and early C promoted spaghetti code, which is intertwined code with many repeated parts. These procedural languages required you to write line after line of code. Although modern languages don't lend themselves to spaghetti code, it's still possible to write it. During the development process, you might copy a routine from one spot to another and later find a bug in it. You would have two pieces of code to correct. This is fine if you are perfect and can remember to change both pieces of code. But this method of programming is hard on upkeep, and it makes reusing code nearly impossible. You would have to start every new project from scratch. Instead of copying a routine to two different objects, consider placing the code in a custom method at the form level or in a library.

Use an Object-Oriented Methodology

Object-oriented programming involves compartmentalizing and reusing code. Although Paradox isn't fully object-oriented, it is object-based and supports many of the concepts of object-oriented programming. Your goal should be to avoid duplicating code by developing self-contained, reusable units. After a while, you will spend the majority of your developing time copying, pasting, and putting together previously developed units. When a bug in a certain unit turns up, you can debug that one unit, and every application from that point on that uses that unit is cleaned up or enhanced. In addition, when you enhance some code that many applications share, those applications are enhanced instantly.

Keep in mind that you can still write spaghetti code in ObjectPAL. If you duplicate parts, you inevitably introduce bugs into your application. ObjectPAL, however, promotes good programming. If you follow the rules of object-oriented methodology, develop in compartments, and avoid duplicating code, your programs will be clean.

Try to Localize Code

Because you're programming in ObjectPAL (an object-based programming language), the code should be as local to the object as possible. For example, if you're trying to decide to put code on a button or the form, then choose the button. If the situation warrants moving up to the form level, it will become obvious. There are many benefits for coding as low as possible, including the capability to copy objects with code on them from one form to another and still have it work. This requires a little extra thought on your part to create a fully self-contained object, but the benefits warrant the extra time.

Tip: Whenever you have a choice, try adding ObjectPAL code directly to the object to get the desired results. Do you ever want to not code locally? Yes--when you want to work with more than one of the same object. You can use a container above all the objects and put code on the object's container.

Code as Low as Possible

Put code on the lowest possible container object. If you later need to use the same code elsewhere, move the code up the container path to the lowest container that both objects can see. If you follow this rule, your code will be compartmentalized and portable. By developing in compartments, you keep code segments apart. A bug introduced in one compartment is less likely to affect other parts of your application.

If you are programming a button, put all the code, including variables, on the button. This makes the button a self-contained unit that is easily copied and pasted to other forms. If you later need that same code on another button, convert it to a custom method and move it up the container path to the button's container object. A container object is an object that completely surrounds and controls the behavior of all the objects within it.

If you then decide you need to use the code with several pages within the form, then move the custom method to the form. If you need the same routine in several forms, consider putting it in a library. A library is an object that stores custom ObjectPAL code. Libraries are useful for storing and maintaining frequently used routines, and for sharing custom methods and variables among forms.

Using this general rule of coding as low to the object as possible gives you maximum access to your code and saves you time. In addition, if you later find a problem with the routine, you need to correct it in only one spot; instantly, all code that uses the routine benefits from the improvement.


Although you can write spaghetti code with an object-based language, ObjectPAL supports and promotes good object-oriented practices. By using contained objects and custom methods properly, you can keep your code clean. Develop in self-contained units whose code is protected from other objects.

When You Code, Think Levels

As I have hinted, in an ObjectPAL application, you put code on various levels. You can put code on objects, on the object's container, on the page, on the form, or even in a library. When you place code in your application, imagine that you are placing your code at various levels. The levels of coding are as follows:

  • Application framework
  • Script
  • Library
  • Form
  • Page
  • Container in a page (a box object, for example)
  • Object on a page or in a container (a field object, for example)
  • Elements of an object (the record object of a table frame, for example)
  • The table level (picture strings and other validity checks, for example)

Application Framework Level

Because the form level is the highest container, creating applications that use multiple forms has special challenges. For example, how do you create a menu system that is in common with all forms? How do you create a separate menu system when displaying reports as opposed to forms? How do you retain a global menu when there are no forms displayed on the desktop? Do you have to duplicate code on every form? Duplicating code is one approach. Another is to pass control to a library and do the executing there. The application framework helps you do this and a whole lot more. When considering what level to place code on, you must consider the application framework levels, too.

Script Level

A script is an object consisting of ObjectPAL code that a user can run independently of a form or that can be called from ObjectPAL. Unlike forms and reports, scripts have no objects. Unlike libraries, scripts can be run without ObjectPAL. You will hardly ever use the script level in a project. It's useful, however, for enabling the user to execute code without opening a form. You occasionally might use the script level to start off an application--perhaps for setting up data for a form, such as adding or deleting records. Another use for scripts is to enable the user to run part of the code without launching the whole application. You will seldom use either of these techniques.

Library Level

A library is a good place to put code that you need to call from multiple forms. Many ObjectPAL programmers think of the library as a way to code above the highest container--the form. For now, just remember that a library is a place to put code that is shared among forms.

Three Form Levels

The form is the top-level UIObject to place code. When using the form level, I like to think of it as actually having three mental levels to place code: the init, the prefilter, and the form level. The init event occurs first and is the first place to consider placing code when you are placing code at the form level. All events go through the form's prefilter, and external events can go to the form twice. First, the event is prefiltered, and then it is sent to the target and can bubble back up to the form. If you want to intercept another object's event, use the form's prefilter. Think of the prefilter as a special level and the else part of the form's if statement as the form level. For example, if you want to do something after the form is opened, use the else portion of the form's arrive event.

If you want to trap for an error so that you can do something in addition to the built-in behavior, use the form's else portion of the error event. If you want to trap for an error before it happens, then most likely the action event is where you will want to code. If you want to write generic code that does something every time you arrive on any one of a set of fields, then use the prefilter of the form's arrive event.

Review of the init, open, and arrive Methods of a Form

The first event to receive a message when a form is opened is the init event, followed by the open event. The arrive event is special in that it occurs when the form is first opened and whenever it is selected. The form's arrive event is a good place to put code that you want executed when the form is first opened and whenever it is selected. For example, you could maximize the form and display a message upon arriving with the following code:

1: ;Form :: arrive

2: method arrive(var eventInfo MoveEvent)

3: if eventInfo.isPreFilter()

4: then

5: ;This code executes for each object on the form

6:

7: else

8: ;This code executes only for the form

9: maximize()

10: msgInfo("Our database", "Welcome")

11: endIf

12: endMethod

With this code, the form maximizes and displays the message every time the form is opened and selected. Compare the preceding routine to the following routine. They both accomplish the same thing: they maximize the application desktop and display a message when the form is opened. The following open version requires a doDefault, however:

1: ;Form :: open

2: method open(var eventInfo Event)

3: if eventInfo.isPreFilter()

4: then

5: ;This code executes for each object on the form

6:

7: else

8: ;This code executes only for the form

9: maximize()

10: doDefault

11: msgInfo("Our database", "Welcome")

12: endIf

13: endMethod

Note in line 10 that doDefault is added after the maximize line. Without the doDefault, the message would interrupt the opening of the form, and the form would look peculiar. For this reason, the arrive event is considered the better, more elegant location.

Review of the Form's Prefilter

People often have trouble using events prefiltered at the form level. If you have 50 fields on a form and want to set the colors on the arrive of each one, it doesn't make sense to add the code to every field's arrive event. Even if the code that sets colors is in a custom method, you don't need to call the custom method in each arrive event. The form's prefilter enables the programmer to write code that intercepts the arrive event for each object and performs the work for each object. Rather than modify 50 fields, the programmer has to deal with only one generic method at the form level. For example, if you want to set the color of every object in the Box class when the form opens, you could do the following:

1: ;Form :: open

2: method open(var eventInfo Event)

3: var UIObj UIObject endVar

4: if eventInfo.isPreFilter()

5: then

6: ;This code executes for each object on the form

7: eventInfo.getTarget(UIObj)

8: if UIObj.class = "Box" then

9: UIObj.color = Red

10: endIf

11: else

12: ;This code executes only for the form

13:

14: endIf

15: endMethod

Using the form's prefilter to work on a group of objects is an important technique. With it, you can cut down on the amount of code you need to write.

Prefilter Example

Suppose that you or your users are having a hard time seeing which field is currently active. One solution is to make the field highlight yellow while it is active. This section demonstrates how to use the form's prefilter to alter the appearance of a field whenever it has focus.

Step By Step

    1. Change your working directory to Paradox's Samples directory and create a form based on the CUSTOMER.DB table. Here's what the form should look like after you create it:

      Illustration 3

    2. Add lines 3--5 and 8--11 to the form's arrive event. Line 4, a UIObject variable ui is declared. In line 8, a handle to the target object is put in the UIObject variable ui. Line 9 uses the handle to test whether it is a field by comparing the property class to the string Field. If they are the same, then a UIObject variable is used to change the field's color to yellow.

      1: ;PREFILT ::Form :: arrive

      2: method arrive(var eventInfo MoveEvent)

      3: var

      4: ui UIObject ;Declare ui as a UIObject.

      5: endVar

      6: if eventInfo.isPreFilter() then

      7: ;// This code executes for each object on the form:

      8: eventInfo.getTarget(ui) ;Set ui to target.

      9: if ui.class = "Field" then ;Is ui a field, then

      10: ui.color = Yellow ;change it to yellow.

      11: endIf

      12: else

      13: ;// This code executes only for the form:

      14:

      15: endIf

      16: endMethod


    3. Add lines 3--5 and 8--11 to the form's canDepart method. This step is the same as step 3 except for location--the code is in the form's canDepart--and it changes the color back to transparent.

      1: ;PREFILT :: Form :: canDepart

      2: method canDepart(var eventInfo MoveEvent)

      3: var

      4: ui UIObject ;Declare ui as a UIObject.

      5: endVar

      6: if eventInfo.isPreFilter() then

      7: ;// This code executes for each object on the form:

      8: eventInfo.getTarget(ui) ;Set ui to target.

      9: if ui.class = "Field" then ;Is ui a field, then

      10: ui.color = Transparent ;make it transparent.

      11: endIf

      12: else

      13: ;// This code executes only for the form:

      14:

      15: endIf

      16:

      17: endMethod

    4. Check the syntax, save the form as PREFILT.FSL, and run it. Move from field to field using the tab key or mouse and note that the active field changes color. Here's what they should look like:

Illustration 4

You can use the form's prefilter whenever you want to work with a group of objects. A group of objects can be categorized by class, color, font, and so on. You can check for all objects in a certain class or for objects with a certain name. You also can check for multiple criteria. For example:

1: ;Form :: isPreFilter

2: if UI.class = "Field" then

3: if UI.name <> "Last_Name" then

4: UI.color = Red

5: endIf

6: endIf

The prefilter of the form's arrive event is a good place for setting colors or other settings that need to be initialized at the beginning of a session, such as user configuration settings. You could write settings to a table or to an .INI file, read them in when the form is opened, and set all the object properties with this technique. More often, however, you will use the other events, such as the action and menuAction events, to manipulate a group of objects. Remember that all the form-level events have a prefilter you can use.

Prefilter and mouseExit

Have you ever noticed that the status bar continues to display the last message even when it's no longer needed? For most applications, this is what you want. Occasionally, however, you might want a cleaner look. You can put message("") in the form-level mouseExit event to turn off messages when a user leaves an object. In addition to cleaning up the status bar whenever you leave an object, this technique enables you to put a message on the mouseEnter event of objects you want to inform the user of. For example:

;Button :: mouseEnter message("This button exits the application")

The Page Level

Normally, your first choice for high-level code should be either a library or the page level. Why not the form level? Because the form level forces the prefilter to fire for every object. In general, avoid the form-level else portion for faster applications. When you have a multipage form and you need to distinguish between pages, use the page level. A classic example of this is when building menus. Generally, an application will need a different menu for every page. In this case, the page's arrive event is the perfect location to build a menu.

In addition, use the menuAction event of the page when you build a custom menu and need to trap for the user's selection. This enables you to have different menus for different pages of a form. If you need the same menu choices on all the pages of a form, use the form-level menuAction event.

The Container Level: Using a Container to Hold Objects

Sometimes it's advantageous to put a container, such as a box object, in a page. Put objects inside the container, and put code at the container level that deals with the objects it contains.

The Container Level: Grouping Objects to Centralize Code

In addition to putting a box around objects, you could group them. This puts an invisible box-type object around the objects. If you want to test something about a group of objects, you can group them and test them with the arrive and canDepart events, for example. In a typical 1:M invoicing system, you might want to verify that a telephone number has been entered, but only when the user attempts to leave the header information. You can group the Header field objects and test the Telephone field on the canDepart event of the grouped objects. For example:

1: ;Group :: canDepart  
2: if Phone.isBlank() then  
3: beep()  
4: message("Enter a phone number.")  
5: eventInfo.setErrorCode(CanNotDepart)  
6: endIf

Grouping Objects to Centralize Code Example

This example demonstrates how to return the values 0 through 9, depending on which object the user clicks. This example uses the technique of grouping objects to centralize code on the grouped object.

Step By Step

    1. Change your working directory to Paradox's Samples directory, create a new form, and place 10 text objects with the values 0 through 9, as shown next. Then, group them by selecting all of them and choosing Format | Group (see Figure 8).

      Illustration 5

    2. Add lines 3--11 to the mouseDown event of the grouped object. Line 4 declares a UIObject variable that is used in line 7 to get the intended object of a mouse action--in this case, the objects in the grouped object. Line 8 changes the object that is clicked to the color yellow, and line 9 notifies the user which object is hit. Line 10 sleeps for one-tenth of a second so that the user can see the object highlighted. Line 11 sets the color back to gray.

      1: ;GROUP :: Group :: mouseDown

      2: method mouseDown (var eventInfp MouseEvent)

      3: var

      4: obj UIObject

      5: endVar

      6:

      7: eventInfo.getObjectHit (obj)

      8: obj.color = Yellow

      9: message ("You pressed the number ", obj.value)

      10: sleep (100)

      11: obj.color = Gray

      12: endMethod

    3. Check the syntax, save the form as GROUP.FSL, and run it. When you click on one of the text objects, it changes color and displays the correct value in the status bar (see Figure 5-4).

Figure 4: GROUPFSL after an object has been clicked

Rather than use the message() procedure in line 9 of step 2 to notify the user of which object was clicked, you might want to use the data for something more practical. For example, you could turn this routine into a calculator.

The Object Level Often Is Best

The optimal place to put code--and also the first place you should think about putting code--is directly on the object itself. Most of the time, you put objects such as fields, buttons, and table frames within a page or in a container and attach code directly to them.

The Elements of Objects Also Is a Level

The lowest level for code is on the elements of an object. Many objects are actually composed of several objects. A field object is composed of a text label object and an edit region object (see Figure 5-5). When you put code on a field, you have a choice of 27 events at the field level, 10 at the edit region level, and 25 at the text label level. In the case of a field, you rarely use the edit region and text levels.

Figure 5: The Object Tree of a field

A button has two levels where you can attach code: the button object and the text object it contains. You usually use the pushButton event with buttons.

Figure 5-6 shows the Object Tree of a table frame. Note that a two-column table frame is composed of seven objects. Normally, you wouldn't place code on the header or on the column labels. That leaves three levels where you can place code on a table frame: the field level, the record level, and the table-frame level.

Figure 6: The Object Tree of a table frame

Figure 5-7 shows the Object Tree of a crosstab. A crosstab is composed of seven objects. Therefore, it has seven places where you can attach code.

Figure 7: The Object Tree of a crosstab

Thinking of attaching code to various levels of an application or object is easier than randomly guessing where to put code. It gives you an idea of what can see what. Whenever a routine isn't behaving properly, ask yourself, "Is there a better level or location for this routine?" Sometimes, especially when you're dealing with multiple objects, the answer is yes.

Declaring Variables at Different Levels

The concepts of various levels, containership, and what can see what are important in ObjectPAL. One of the most important elements of ObjectPAL code placement is where to declare a variable. The discussion of scope and instance in this section also applies to the other object windows: Const, Type, Uses, and Proc.

The place where you declare a variable determines the scope and instance of a variable. The term scope means accessibility. The scope of the variable means what other objects, if any, can see it. The scope of a variable--that is, the range of objects that have access to it--is defined by the objects in which it is declared and by the containership hierarchy. Objects can access only their own variables and the variables defined in the objects that contain them. The scope of a variable depends on where it is declared.

The instance of the variable means how long the variable exists. For example, if you declare a TCursor variable within a button and you want to use the same TCursor variable in another button, you could declare the TCursor in both buttons, which is a waste of resources. Or, you could move the declaration to higher ground--that is, to a place where both buttons can see and reuse one variable. In the case of two buttons on a page, the page level is a good choice. All the objects on the page, including the two buttons, have access to page-level variables.

Declare Private Variables Within a Method

After you choose which object to declare a variable on, you must decide whether you want the variable to be private to a method or global to the object. Variables declared within a method are visible only to that method and are dynamic; that is, they are accessible only while that method executes. They are initialized (reset) each time the method executes. A private variable can be seen only by the event in which it is declared. Its scope is limited. Therefore, if you want to use the variable in only a single method, use a variable that is private to the method.

If you declare a variable within a method (either within or above method...endMethod), then the variable is private to the method; that is, no other methods or objects can see or use the variable. In essence, the variable is private to the method.

Most often, the first place you choose to put a variable is inside method...endMethod. When you do this, the variable's scope is limited to the method and its existence is only for the duration of the method. Use this technique when no other objects need to use the variable and the variable can be initialized each time. For example, you can put both the variable declaration and the code in the same method window, as in the following:

1: ;Btn1 :: pushButton  
2: method pushButton(var eventInfo Event)  
3: var ;Private variables are declared  
4: s String ;inside method/endMethod.  
5: endVar  
6:  
7: s = "Hello World"  
8: msgInfo("", s)  
9: endMethod

The first technique is easier to read; all code is located in the same place. In addition, the variable is private, or local. More specifically, the variable is local to only this method; no other events of this object or of another object can see the variable.

Declare Global Variables in the Var Window

The Var window of an object creates a variable that is global to the object. Variables declared in an object's Var window are visible to all methods attached to that object, and to any objects that object contains. A variable declared in an object's Var window is attached to the object and is static, accessible as long as the object exists.

A variable with broader scope in ObjectPAL is said to be global to the object. Any object can access it from that point down in the containership hierarchy. Do not confuse the concept of a variable being global to an object with a global variable in other languages. A variable that is global to an object in ObjectPAL is global only to that object and not to any other objects.

After you choose the object, you have three places in which you can declare a variable: in the Var window of the object, inside the method...endMethod structure of a method, or above the method...endMethod structure.

As an alternative to putting the variable declaration with the code, you can put the variable in the Var window and the code in the method, as in the following:

1: ;Btn2 :: Var  
2: var ;Global to an object variables  
3: s String ;are declared in the Var window  
4: endVar ;of the object.    
1: ;Btn2 :: pushButton  
2: method pushButton(var eventInfo Event)  
3: s = "Hello World"  
4: msgInfo("", s)  
5: endMethod

This second technique uses a variable that is global to the object. It is more elegant if you need to use the variable elsewhere. The variable is global to the object; all the other methods of the object and of the objects it contains can see the variable. In other words, the scope you need for a particular variable is the determining factor. If no other object needs the variable, declare it privately.

Are there any exceptions to this rule? Yes. In the preceding example, either inside method...endMethod or in the Var window is equally elegant because the button's pushButton method occurs only once for each click of the button, which doesn't tax the system. In fact, you can declare a variable in any custom method or event that executes once. Methods such as pushButton, open, and arrive are prime candidates for declaring variables privately inside method...endMethod.

In the case of an event such as newValue, which is triggered many times by many different events, the second technique of separating the variable in the Var window from the code in the event is more elegant. Typically, the newValue event is triggered so many times during a session that redeclaring a variable each time doesn't make sense. A variable could be redeclared thousands of times in a newValue event.

Var window variables are declared for the instance of the object. Therefore, they are more elegant in most cases. It's better programming practice to declare your variables only once so that the system won't be taxed. The declaring of variables in the Var window occurs only once, and it occurs even before the open event of the object. If you want to see this for yourself, put the following code on a button and run the form. The message box will display the correct variable declaration.

1: ;Button :: open  
2: method open(var eventInfo Event)  
3: msgInfo("", dataType(o))  
4: endMethod  
1: ;Button :: Var  
2: var  
3: o OLE  
4: endVar

When you declare variables, you usually use one of the two techniques just discussed. The scope and instance of the variable are the determining factors. In general, it's a good rule of thumb to use the Var window as your first choice. Move the declaration from the Var window (which is global to the object) to within method...endMethod (which is private to the method) only when needed. By putting variables in the Var window of an object, more events have access to it.

Declaring Variables Outside a Method Block

What if you want the scope of a variable to be private to a method, but have its instance be for the duration of the object? Is this possible? Yes. Variables declared before the word "method" in a method...endMethod block are visible only to that method and are static. This technique has the benefit of declaring the variable only once--that is, when the object is created--yet the variable remains private to the method. In addition, its existence is for the duration of the object.

1: ;Button :: pushButton  
2: var  
3: O OLE ;Private variable declared only once.  
4: endVar  
5: method pushButton(var eventInfo Event)  
6: msgInfo("", DataType(O))  
7: endMethod

This third technique is not used often enough. It is an elegant way to declare a variable private to a method, because the variable is declared only once. Declare permanent variables in either the Var window (scope that is global to the object with an instance of the duration of the object) or above the method...endMethod (scope that is private to the method with an instance of the duration of the object).

Variable Level Summary

After you decide that you need a variable that is global to an object--in other words, you have decided to use the Var window of an object--you must decide the level on which you want to declare the variable, such as the form, the page, the object's container, or the object. The answer depends on what you want the scope of the variable to be. In a way, it depends on how global you need the variable to be. In general, I declare the variable either at the form level or at the lowest possible container. If you declare a variable at the form level, you don't have to worry about the scope of the variable because the variable is global to the form. That is, all objects in the form can see it.

The better of these two approaches is to declare variables in the Var window of the lowest possible container. You can move them up the containership path when a broader scope is needed. For example, if you're going to use a variable on a button, declare it on the button. Declare the variable in the event itself--for example, within the pushButton event. If you use it in another of the button's events--for example, mouseEnter--move it to the button's Var window.

If you later need that variable for another object on that page or on another page in the form, move the variable back to the Var window of the page or form. In general, declare the variable on as low a level as possible, and use the Var window whenever you need the same variable with two different events or two different objects.

Examples of Where to Put Code

Good examples are valuable. The following examples of where to put code might not be the most elegant, but they're good:

  • Field validation Validate fields at the object level using the canDepart or changeValue events.
  • Record validation Validate records at the record level with the action or canDepart events.
  • Initialize variables Initialize variables in the form's init event. In general, avoid the form's open event except for routines that deal with every object in the form.
  • Universal constants Declare often-used constants in a library and use the extended Uses syntax.
  • Universal custom types Define often-used custom types in a library and use the extended Uses syntax.
  • Scripts If you have a routine that needs to be called by code and run independently by a user, then use a script.

Summary

This chapter covered many aspects of ObjectPAL, including objects, containers, containership hierarchy, and bubbling. One of the hardest aspects of using ObjectPAL is deciding where to place code. Novice ObjectPAL programmers tend to place code everywhere. Rather than work with the default behavior, the event model, and the data model, beginners often tend to put code in the wrong place. Later, they add more code to try to fix an inelegant approach.

Taking the time to understand the default behavior of objects, the event model, and the data model can save you much time. If you don't fully understand how an object works, you could end up working harder and accomplishing less. Much of this book deals with this issue. This chapter concentrates on it and offers guidelines, tips, and techniques on where and when to code.

Where code is placed is important. When coding in any event-driven environment, think levels. In ObjectPAL this extends from the library through objects on a form. Ultimately, you must find the perfect place to put your code. Only through trial and error--and some experimentation--will you learn the good and bad places to put code. The guidelines presented in this chapter will get you started.


Comments

1 Comments.
Share a thought or comment...
Comment 1 of 1

Where's the article itself?  The only thing that's posted here is the single-sentence "Take Away" message.... (viewing http://www.prestwood.com/ASPSuite/KB/Document_View.asp?QID=100868&eMag=201306 from search results)

---
Philip Thomas
Posted 6 years ago
 
Write a Comment...
...
Sign in...

If you are a member, Sign In. Or, you can Create a Free account now.


Anonymous Post (text-only, no HTML):

Enter your name and security key.

Your Name:
Security key = P1237A1
Enter key:
Article Contributed By Mike Prestwood:

Mike Prestwood is a drummer, an author, and creator of the PrestwoodBoards online community. He is the President & CEO of Prestwood IT Solutions. Prestwood IT provides Coding, Website, and Computer Tech services. Mike has authored 6 computer books and over 1,200 articles. As a drummer, he maintains play-drums.com and has authored 3 drum books. If you have a project you wish to discuss with Mike, you can send him a private message through his PrestwoodBoards home page or call him 9AM to 4PM PST at 916-726-5675 x205.

Visit Profile

 KB Article #100868 Counter
13518
Since 4/2/2008
-
   Contact Us!
 
Have a question? Need our services? Contact us now.
--Mike Prestwood

Call: 916-726-5675

email: info@prestwood.com


Go ahead!   Use Us! Call: 916-726-5675 


©1995-2019 Prestwood IT Solutions.   [Security & Privacy]