Objects that you place on forms and reports are UIObjects (user interface objects). Only UIObjects that you place on forms contain events. The form itself is also a UIObject—it has events and responds to events.
Pixels and Twips
A pixel is an abbreviation for picture element. Pixels vary in size depending on your monitor. One physical pixel is one dot on your screen, regardless of resolution. Contrast this with a twip, which is a physical unit where 1,440 twips are equal to one inch, regardless of resolution. Most ObjectPAL properties that manipulate size are in twips, not pixels.
Using the UIObject Variable
ObjectPAL offers tremendous flexibility in manipulating UIObjects during run time. You can either directly refer to a UIObject or attach to it and refer to it with the UIObject variable. For example, assuming a box named box1 is on a form, you can change its color with the following:
method pushButton(var eventInfo Event)
box1.color = Red
endMethod
or with
method pushButton(var eventInfo Event)
var
ui UIObject
endVar
ui.attach(box1)
ui.color = Red
endMethod
Obviously, in most situations the first single line of code is simpler, but the second technique is a particularly useful technique that you will often use when you do not know the name of the object a routine will be working with.
Moving a Text Box During Run Mode
Enabling the user to move objects around during View Data mode is very useful. In ObjectPAL, you can enable the user to move an object around to reveal something behind it, or you can enable the user to move an object to a new location. The following example shows you how to move an object around. When you let go of the mouse button, the mouseUp event occurs and the code in it snaps the object back to its original position. You could use this technique for many tasks. For example, you could use this technique in a game to reveal answers or offer clues in an educational game.
Step By Step
Create a form with several text boxes on it. Give them various frames and colors (see Figure 17-1).
Figure 1: Setup form
Add lines 3–5 to the Var window of the form.
1: ;Form :: Var
2: Var
3: x,y,x1,y1,w,h SmallInt
4: ui UIObject
5: sTargetClass String
6: endVar
Add lines 8–10 to the mouseDown event of the form:
1: ;Form :: mouseDown
2: method mouseDown(var eventInfo MouseEvent)
3: if eventInfo.isprefilter() then
4: ;This code executes for each object on the form
5:
6: else
7: ;This code executes only for the form
8: eventinfo.getTarget(ui)
9: ui.getPosition(x1, y1, w, h)
10: sTargetClass = ui.class
11: endIf
12: endMethod
Add lines 3–5 and 11–17 to the mouseMove event of the form:
1: ;Form :: mouseMove
2: method mouseMove(var eventInfo MouseEvent)
3: var
4: liX, liY LongInt
5: endVar
6: if eventInfo.isprefilter() then
7: ;This code executes for each object on the form
8:
9: else
10: ;This code executes only for the form
11: if eventinfo.isLeftDown() and
12: sTargetClass = "Text" then
13: liX = eventinfo.x()
14: liY = eventinfo.y()
15: ui.getPosition(x, y, w, h)
16: ui.setPosition(x + liX - 400, y + liY - 400, w, h)
17: endIf
18: endIf
19: endMethod
Add line 8 to the mouseUp event of the form:
1: ;Form :: mouseUp
2: method mouseUp(var eventInfo MouseEvent)
3: if eventInfo.isprefilter() then
4: ;This code executes for each object on the form
5:
6: else
7: ;This code executes only for the form
8: ui.setPosition(x1,y1,w,h)
9: endIf
10: endMethod
Check the syntax, save the form as MOVER.FSL, and run the form. Click and drag any text box you placed on the form to move it. When you let go (mouseUp), the object snaps back to its original location (see Figure 17-2).
Figure 2: MOVER.FSL demonstrates moving objects during run time
Creating UIObjects
ObjectPAL enables you to create objects in run time. To create objects on the fly, you need to use the create() method, and you need to know about things such as points and twips. As stated earlier, a twip is what you use to measure points on the screen. A point has a x value and a y value, both of which are measured in twips. A twip is 1/1,440 of an inch, or 1/20 of a printer’s point. The following two examples use the create() command and a point variable to create and delete a line. The create() method does just what its name implies—creates objects. The type of object, where the object is created, and the dimensions of the object are all specified as part of the parameters for create().
The properties (frame, color, font, and so on) of the object produced by create() default to whatever the object defaults happen to be at the time the object is created. To modify the properties, you must change each individually, after the object has been created. The notation and properties specific to graphs are discussed later in this chapter.
The parameters for create() are as follows:
objectType
Corresponds to the type of object being created. For graphs, this will be ChartTool.
x
The x coordinate (in twips) of the upper-left corner of the object.
y
The y coordinate (in twips) of the upper-left corner of the object.
w
The width (in twips) of the object to be created.
h
The height (in twips) of the object to be created.
container
This is an optional parameter. If present, container must be a UIObject capable of containing the created object. In other words, you cannot place a 1,000 × 1,000 object within a 500 × 500 container.
A Note on Working with Groups
Normal object creation goes from the outside to inside. For example, first the page is created and then you place a box on the page. Within the box, you place a table frame. Obviously, you cannot create the box without first creating the page. Likewise, you cannot create the frame without first creating the box. Unfortunately, groups work the opposite way. You cannot place a group on the page and then place objects within the group. You must first place the two objects and then place a group around these two objects.
Working with Frames
Because frames can vary in thickness, you need to allow for a border on the containing object. As a general rule of thumb, it is a good idea to give 15-twips distance between the inner object and its containing object.
Creating a Line
Suppose that you want to allow the user to create and delete a line. To do this, you use two buttons on a form. One button creates a line, and the other deletes it.
Check your syntax, save the form as CreateLine.fsl, switch to View Data mode, and click the Create Line button. After the line is created, click the Delete Line button to remove it (see Figure 17-4).
Figure 4: CreateLine.fsl demonstrates creating and deleting a line
Having Fun with Lines
Now let’s have a little fun with creating lines. This next example randomly creates lines of varying length, position, color, and thickness in a box.
Step By Step
Create a new form and place two buttons, two fields, and a box on it (see Figure 17-5).
Label one button Create Line and the other Delete Line. Change the upper field name to fldLines and label it # of Lines. Change the lower field’s name to fldDelay and label it Delay (see Figure 17-5).
Figure 5: Setup form for the example
Change the name of the page to pge and the box to boxLines.
Alter the open event of the fldLines field as follows:
14: ; Get thickness of box’s frame (plus a little for margin).
15: siFrameSize = boxLines.Frame.Thickness + 15
16: ; Get coordinates of box.
17: pBox = boxLines.fullSize
18: x = pBox.x() - siFrameSize
19: y = pBox.y() - siFrameSize
20: w = pBox.x() - siFrameSize
21: h = pBox.y() - siFrameSize
22: for siCounter from 1 to fldLines.value
23: ;Set value for xTemp.
24: xTemp = 0
25: while (xTemp <= siFrameSize)
26: xTemp = smallInt(rand() * x)
27: endWhile
28: ;Set value for yTemp.
29: yTemp = 0
30: while (yTemp <= siFrameSize)
31: yTemp = smallInt(rand() * y)
32: endWhile
33: ;Set value for wTemp.
34: wTemp = w
35: while (xTemp + wTemp) >= w
36: wTemp = smallInt(rand() * w)
37: endWhile
38: ;Set value for hTemp.
39: hTemp = h
40: while (yTemp + hTemp) >= h
41: hTemp = smallInt(rand() * h)
42: endWhile
13: for siCounter from 1 to arLines.size()
14: if arLines[siCounter] <> "pge.boxLines" then
15: uiLine.attach(arLines[siCounter])
16: uiLine.delete()
17: endIf
18: endFor
Check your syntax, save the form as CreateLines.fsl, switch to View Data mode, and click the Draw Lines button. After the lines are created, either click the Draw Lines button again or click the Clear Lines button to remove them (see Figure 17-6).
Figure 6: CreateLine.fsl demonstrates creating and deleting a line
Creating a Graph from Scratch
Now that you’ve seen how easy the create() method is, type the following code into the pushButton event of a button:
The preceding code creates a graph from scratch on a form. ObjectPAL is powerful; it enables you to create all types of objects while the object to which the code is attached is running. You also can alter the properties of graphs already created.
A Simple Example of Manipulating a Graph Property
This next example is similar to the preceding example, but adds the manipulation of the bindType and graphType properties to set the graph type. Following is another example you can type into the pushButton event of a button:
Sometimes you need to open another form, alter it, and close it from ObjectPAL. When you do, you are prompted to save the changes to the form; this is a very undesirable feature for a finished application. You could deliver the form and the problem would go away, or you could use the designModified property of the form, as in the following example:
1: f.designModified = False
In essence, you are telling the form that nothing was changed, when in fact it was.
This technique also works with reports. For example, the following code snippet is from our Paradox Workbench commercial utility—the code snippet opens a report and alters it:
1: var
2: r Report ;Declare r as a report variable.
3: ri ReportOpenInfo ;Declare ri as a ReportOpenInfo variable.
4: endVar
5:
6: ri.name = "SOURCE" ;Specify report name.
7: ri.masterTable = "SOURCE.DB" ;Set master table for report.
8: r.open(ri) ;Open report.
9:
10: ;Set the value property of a text object.
11: r.txtTitle.value = "Source Code for " + fldFileName.value
12: r.show()
13: r.designModified = False ;Tell report it has not changed. 14: r.wait()
Dereferencing a UIObject
You can create, move, size, and generally change any property of a UIObject. But how do you reference a UIObject with a variable? As discussed previously, you do so through dot notation and the use of parentheses. Referencing objects without hard-coding their names in the application adds flexibility to your routines.
When you work with several objects on a form, you might want to perform the same actions on each of the objects at different points in the code. One technique that saves many lines of code is to use a variable to reference an object. Remember the following three rules when you use a variable to reference an object:
The statement that references an object must include a containership path. In the example that follows, Page refers to the name of the actual page in which the (Y) object resides.
The first object in the path must not be a variable.
Parentheses must surround the name of the variable..
Following is an example of how these rules are applied:
1: for X from 1 to 10
2: Y = "Box" + strVal(X) ;This evaluates Y = "Box1" for
3: Page.(Y).color = Blue ;the first iteration of the
4: ;for loop
5: endFor
This code changes the color of the objects named Box1 through Box10 to the color blue. Remember that it’s easier to access the objects if you rename them yourself. If the name of the object is the previous name suffixed by a number, such as Box1, you can use code.
Tip: If you need to rename many objects one right after the other, use the Object Tree. By selecting and inspecting each object on the Object Tree, you can quickly rename many objects.
Using Timers to Animate a UIObject
Timers in ObjectPAL offer a powerful way to manipulate your environment. A timer enables you to execute an event every so many milliseconds. To set a timer, use setTimer(), as in the following:
1: setTimer(milliSeconds [,repeat])
For example, on the open event of any object, you can set a timer to trigger every 10 seconds:
1: self.setTimer(10000)
After you set the timer, add the code that you want to execute on the timer event of the object, as in the following example:
You can use timers for a multitude of tasks, such as the following:
Executing a set of commands every n milliseconds
Checking the system time or date to set scheduled events
Looping every n milliseconds for a multitasking looping technique
Animating objects by moving and resizing them
Animating a Box Across the Screen
This next example demonstrates how to move an object across the screen. It uses the timer event and the position property to move a box across the screen. When the Box object has reached the other side, it starts over.
Step By Step
Create a new form with a box measuring approximately one-inch by one-inch (see Figure 17-7).
Figure 7: Setup form for the example
Add lines 3 and 4 to the Var window of the box.
1: ;Box :: Var
2: Var
3: posPt Point
4: x,y LongInt
5: endVar
Add lines 3–6 to the open event of the box.
1: ;Box :: open
2: method open(var eventInfo Event)
3: self.setTimer(100)
4: posPt = self.position
5: x = posPt.x()
6: y = posPt.y()
7: endMethod
Add lines 3–7 to the timer event of the box.
1: ;Box :: timer
2: method timer(var eventInfo TimerEvent)
3: x = x + 50
4: self.position = Point(x, y)
5: if x > 5800 then
6: x = 200
7: endIf
8: endMethod
Check your syntax and run the form. The pull-down menus still work even though the code is executing (see Figure 17-8).
Figure 8: Demonstration of animating a UIObject
In step 3, line 3 starts the timer so that the code will execute 10 times a second (100/1000 = 10). Line 4 from step 2 declares posPt as a point variable so that you can get the position of the box in line 3. Line 4 in step 2 declares x and y long integers to store the values in lines 5 and 6 of step 3.
Line 3 in step 4 increments x by 50 for use in line 4 to move the box horizontally to the right by 50 twips. Lines 5 and 6 check whether the box has traveled as far to the right as you want. If it has, line 6 repositions it to the left.
You can have a lot of fun animating your forms with timers. As an exercise, add objects to this form. You could even introduce a random moving of objects. Timers have two basic uses: for timed events and for multitasking. Use timers when you need to execute a set of commands repeatedly, or when you need to multitask one task with another.
Looping with a Timer
Suppose that you want to enable a user to continue using his or her computer during a while loop that will take a long time to complete. To do this, you need to return control to Windows 95 or Windows NT. You won’t see function calls in ObjectPAL that are equivalent to WaitMessage in the Windows SDK. How do you handle this situation? Because it’s part of the Windows API, you can call WaitMessage directly. To do this, declare it in a Uses statement and call it. There are, however, two better and easier techniques.
You can use two techniques, depending on how much control you want to give back to Windows. You can insert a sleep() statement in your while loop, which yields to Windows events. Depending on how complicated the while loop is, this might give you enough of a yield. You can add more sleep() statements to your code, or you can recode it to use the built-in timer event.
Set a timer event on a UIObject to fire every x milliseconds. You set x. Then, place one iteration of the while loop on the timer event. The iteration of the loop will process. You can vary how much you do on each timer event; a single iteration is the simplest example. Of course, you’ll remove the while statement because the timer event controls the repetitive processing.
Suppose that you want to add three fields to a form that count up while still enabling users to use the form and Windows. You can use this technique to create loops that enable users to continue their work. In this example, you set up three independent loops that use timers and three buttons that control the three loops as a set. The first button starts the looping process. The second button causes the three loops to pause. The third button kills all three loops. To show that these three loops are multitasking, you add a table frame connected to a table. That way, you can add records while the three loops count.
Set your working directory to Paradox’s Samples directory. Create a new form, based on the Customer table, and add three buttons on it. Label the buttons Start Timers, Pause Timers, and Kill Timers. Add three unlabeled undefined fields. Name them Field1, Field2, and Field3. Figure 17-9 shows how the form should look. In this figure, the three undefined fields and font sizes have been enlarged, and most of the columns have been deleted from the table frame.
Figure 9: Setup form for the looping with timers example
Type line 3 in the Var window of the form.
1: ;Form :: Var
2: Var
3: Counter1, Counter2, Counter3 SmallInt
4: endVar
Check the syntax, save the form as LOOP-T.FSL, and run the form. Click the Start Timers button and let it run a while. All three loops run at different speeds. You can use this effect to prioritize tasks. Click on the Pause Timers button; all three loops pause. When you click the Start Timers button a second time, the loops continue from where they paused. Now, use the table frame. For example, scroll up and down a few records, insert a record, and so on. Click the Kill Timers button to stop and reset all three loops. Figure 17-10 shows how the form should look after you finish this example.
Figure 10: Using timers to create multitasking
In step 2, line 3 declares the three variables used in the three timers.
In step 3, lines 3–5 initialize the variables declared in step 2 line 3 when the form is opened.
Except for the number of times that the timers loop, the three timers are the same. In step 4, line 3, the first loop checks whether the counter variable is less than 100. If it is, line 4 increments it by 1. Line 5 sets the value of self to the value of the counter variable. This shows progress through the loop; normally, you would do something more productive. If the counter variable in line 3 isn’t less than 100, line 7 sets it to 0. Line 8 sets the value of the field to 0 to indicate visually that the loop is over. Line 9 destroys the timer.
In step 7, lines 3–5 on the Start Timer button start the looping process. They dictate which loop has priority—that is, the loop speed. Line 3 sets the first timer to fire once every second and starts it. Line 4 sets the second timer to fire once every quarter second and starts it. Line 5 sets the third timer to fire once every one-twentieth of a second and starts it.
In step 8, lines 3–5 on the Pause Timer button kill the timers but don’t reset the counter variables. This enables the three loops to pause and restart.
In step 9, lines 3–13 kill and reset the timers, counter variables, and field values. Using timers to multitask and pause loops is an important technique. It often will come in handy.
Summary
In this chapter, you learned that ObjectPAL offers tremendous flexibility in manipulating and creating UIObjects during run time. You learned that the form itself is both a display manager and a UIObject—it has events and responds to events. You can create, move, size, and generally change any property of a UIObject. You need to know about things such as points and twips. A point has a x value and a y value, both of which are measured in twips. Most ObjectPAL properties that manipulate size are in twips, not pixels. A twip is what you use to measure points on the screen.
The ObjectPAL language gives you the capability to dereference objects and use timers. You can either directly refer to a UIObject or attach to it and refer to it with the UIObject variable. Referencing objects without hard-coding their names in the application adds flexibility to your routines. Timers in ObjectPAL offer a powerful way to manipulate your environment. Timers have two basic uses: for timed events and for multitasking. Use timers when you need to execute a set of commands repeatedly, or when you need to multitask one task with another.