This chapter addresses syntax. Its intent is to make you feel more comfortable with ObjectPAL. After reading and entering in all the examples in this chapter, you will have a much better grasp on how to write ObjectPAL code.
When is Paradox case sensitive? Paradox is case sensitive with string comparisons and locates. For example, Yes is not the same as YES when you compare strings. When you locate a record, Lupton is not the same as lupton or LUPTON. Case sensitivity applies even when you check against parts of the ObjectPAL language if the part is in a string. For example, box isn’t the same as Box. You can check which class an object belongs to with the following:
1: var ui UIObjectendVar
2: eventInfo.getTarget(ui)
3: if ui.class = "Box" then
4: ;Put your code here. For example:
5: message(ui.name)
6: endIf
If you accidentally typed box or BOX, the routine would fail. Therefore, you need to watch your string comparisons. You can use ignoreCaseInStringCompares() and ignoreCaseInLocate() procedures, however, to force case insensitivity, as in the following example:
1: var ui UIObjectendVar
2: eventInfo.getTarget(ui)
3: ignoreCaseInStringCompares(Yes)
4: if ui.class = "box" then
5: ;Put your code here. For example:
6: message(ui.name)
7: endIf
Other than string compares and locates, ObjectPAL syntax is not case sensitive. The mixture of uppercase and lowercase characters you use when writing ObjectPAL keywords, methods, procedures, variables, and so on, is up to you. Appendix A goes into detail on this subject.
Declaring variables already has been introduced by implication because variables have been used in many examples before now. What follows in this section is a formal introduction/review of declaring variables in ObjectPAL. You declare variables in a var block, as in the following example:
1: var ;Start of variable block.
2: ;Declare variables here.
3: endVar ;End of variable block.
When you declare variables of the same type, you can either put them on the same line or separate lines. The choice is yours; it does not matter to the compiler. For example, the following declares four variables—two numbers and two strings.
1: var
2: s1 String ;You can declare like variables on
3: s2 String ;separate lines, or
4: n1,n2 Number ;on the same line separated by commas.
5: endVar
Following are some examples of how variables are declared:
1: si SmallInt ;Declare si as a small integer. This
2: ;is useful for smaller numbers.
3: s String ;Declare s as a String.
4: f Form ;Declare f as form.
5: app Application ;Declare an application variable
6: ;when you want to deal with the
7: ;desktop. For example,
8: ;to maximize or hide the
9: ;application.
10: t Time ;Declare t as type
11: ;time to deal with time.
Following are some examples of using variables in expressions:
1: x = x + 1
2: s = "Nicole Kimberly Prestwood"
3: s = "The new value is " + String(x)
4: s = "The time is " + String(t)
5: message(s)
ObjectPAL offers built-in variables in addition to its events. These variables are predefined by Paradox for you to use in your ObjectPAL routines. The built-in object variables are an integral part of ObjectPAL. Like so much of ObjectPAL, they are self-explanatory. In fact, you’ve already used several of them. The built-in object variables are self, subject, container, active, lastMouseClicked, and lastMouseRightClicked. Use the built-in object variables as much as possible. They make your code very readable and more portable.
With one exception, self always refers to the object to which the code is attached. The exception is when self is used in a library. When self is used in a library, it always refers to the object that calls the custom method. For now, concentrate on using self within code on an object like a field.
Use self whenever possible; it makes your routines more generic. Use self whenever code refers to itself. If you have a choice between the following two lines of code, always choose the one that uses self:
If you later change the name of the Last_Namefield, you don’t have to change the name used in the code. In addition, if you copy or move the code to an object with a different name, it will still work. Finally, when you copy an object with code attached to it, the code is more likely to work if you use self.
When self is used in a library, it refers to the calling object. Wouldn’t it be nice if you could do the same with custom routines in a form? You can, with subject. With dot notation and subject, you can have a routine work on another object.
When you use container in your code, it refers to the object that contains the object to which the code is attached. If you have a small box inside a large box and you execute the following code attached to the small box, the large box turns red:
1: ;SmallBox :: mouseClick
2: container.color = red
refers to the object that is currently active; in other words, the last object to receive a moveTo() command. Usually, it’s the object that currently has the focus; that is, the object ready to be edited. In the following example, active is used to indicate the object that has the focus:
1: active.nextRecord()
In a multitable form (a form with several tables bound to it), this line of code acts differently, depending on which object has the focus.
refers to the last object to receive a mouseDown/mouseUp combination. A mouseDown/mouseUp combination occurs whenever the user presses the left mouse button while the pointer is over an object. This variable enables you to deal with the user’s most recent mouse clicks without writing special code. lastMouseRightClicked is the same as lastMouseClicked, except that the right mouse button is pressed.
The built-in object variables—self, subject, container, active, lastMouseClicked, and lastMouseRightClicked—are used in developing generic routines. Generic routines are important. The more generic routines you use, the less coding you have to do.
You can use any property that applies to the type of object with which you are working. Properties that don’t apply won’t work. For example, because buttons don’t have frames or color properties, self.color = Red doesn’t work on a button.
What are data types? Data typing is the classifying of data into categories such as string, number, and date. This classification is necessary so that the compiler knows how to treat and store the data. For example, a number takes up more room in memory than a small integer. In ObjectPAL, you can declare a variable to be any of the many variable types.
How do you know the available types? You already know the answer to that question. When you browse through all the variable types and their methods by selecting View | ObjectPAL Quick Lookup. Those are the variable types you can declare a variable as. The methods and procedures that correspond to them are the methods and procedures you use to manipulate them.
To create an up-to-date table of class names, create and run the following one-line script:
enumRTLClassNames("RTLCLASS.DB")
This one line of code creates a table with all the current class names. Why should you do this if the preceding table has all the classes? When a new version of Paradox is released, you can run this same script and check to see whether any new classes of objects were added.
As soon as you find a variable type with which you want to work, you declare a variable as that type in a variable block. As soon as the variable is declared, you can use any of the methods in its class to manipulate it. For example, as soon as a TCursor is defined, you then can use any of the TCursor object type or class of methods to manipulate it:
1: var ;This is the start of a var structure.
2: tc TCursor ;Set variable types in between.
3: endVar ;This marks the end of a var structure.
4: tc.open("ZIPCODES.DB")
The tc variable is a handle to a TCursor object. A TCursor is a type of object. How do data types relate to object types? Data types are simply a very important subset of all the variable types. Remember that in ObjectPAL there are more than 50 object types. All these object types are variables that you can manipulate with the appropriate method or procedure. For example, you use the data types to store data in memory. As soon as data is stored in a particular type of variable in memory, you can use the methods associated with the type or class of variable to manipulate it.
Although the Paradox table structure supports many data types, as you can see, ObjectPAL picks up where Paradox and dBASE tables leave off. There are more ObjectPAL data types than either Paradox or dBASE field types. If you wanted to store time in a dBASE table, what field type would you choose? dBASE, unlike the Paradox table structure, does not offer a time format. One technique is to use a character field of size 12 to store the characters and then use ObjectPAL to manipulate them. The important thing to note at this point is that the data types in ObjectPAL enable you to deal with more types of data than the set Paradox and dBASE field types allow. You declare or cast a variable as a particular type of variable. This is known as typecasting.
is the catch-all variable type for the times when you can’t predict what type of variable you’ll need. After you declare a variable as an AnyType variable, you can use the seven AnyType methods and procedures to manipulate it. These methods are very important because most of them are the core methods for the rest of the data types. Following is a description of each:
blank()
Returns a blank value.
dataType()
Returns a string representing the data type of a variable.
isAssigned()
Reports whether a variable has been assigned a value.
isBlank()
Reports whether an expression has a blank value.
isFixedType()
Reports whether a variable's data type has been explicitly declared.
Whenever the compiler comes across a variable or value in your code, it stores it in memory. If the value was not typecast, then the compiler casts it at that time. This can create problems. For example, if you type and run the following code, you’ll get an error:
1: ;Button :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: n Number ;Declare n as a number.
5: endVar
6:
7: ;Set n to 40000.
8: n = 2 * 20000 ;This gives an error.
9: n.view()
10: endMethod
Why? Isn’t 40,000 within the range of a Number? Yes, but when ObjectPAL came across the equation 2 * 20000, it had to store the numbers in memory and, because the first number is an integer, the numbers were stored in temporary small integer variables. Small integers cannot store the value 40,000. To fix this, change the preceding line 8 to the following.
8: n = 2.0 * 20000.0
More on implicit casting of numbers later when SmallInt, LongInt, and Number are discussed. For now, following are some examples of implicit casting that you can type into a button if you want:
1: x = 1 ;x is cast as a SmallInt.
2: view(Datatype(x)) ;Displays SmallInt.
3:
4: x = 1.1 ;x is cast as a Number.
5: view(Datatype(x)) ;Displays Number.
6:
7: x = 40000 ;x is cast as a LongInt.
8: view(Datatype(x)) ;Displays LongInt.
9:
10: x = "Clark Lupton" ;x is cast as a String.
11: view(Datatype(x)) ;Displays String.
In the next example, you use the same variable as a number and then as a string without declaring it again. It demonstrates using an AnyType variable. Suppose that you need to use x as both a number and a string. In this example, x is used first as a number, then as a string.
Open the pushButton event of the button and add lines 3–13 to it. Line 4 declares x as an AnyType variable, which means that you can store many types of data in it without declaring it again. Line 7 sets x to the number 12.3. It is viewed with the Number class viewer in line 8. In line 9, msgInfo() uses dataType() to confirm that the type of x has been declared by ObjectPAL as type Number. Lines 11–13 are just like lines 7–9, except that in line 11, x is set to the string 12.3. In line 12, the string viewer is used when the user is shown the value. In line 13, msgInfo() confirms that, internally, ObjectPAL has changed the data type to a String type.
1: ;DATATYPE :: Button :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: x AnyType ;Declare x as AnyType.
5: endVar
6:
7: x = 12.3 ;First use x as a Number.
8: x.view() ;View x.
9: msgInfo("DataType", dataType(x))
10:
11: x = "12.3" ;Then use x as a String.
12: x.view() ;View x.
13: msgInfo("DataType", dataType(x))
14: endMethod
Check your syntax, save the form as DATATYPE.FSL, and run it. Click the button. First, you see the number 12.3 in a number-style viewer. After you click OK, you see a message box confirming that the current data type for x is indeed Number. After you click OK to the message box, you see the characters 12.3, but this time they are in a string box. After you click OK to the viewer, a message box confirms that x indeed is now a String.
Although x was specifically declared as an AnyType variable in the preceding example, declaring an AnyType variable isn’t required. Whenever the ObjectPAL compiler comes across a variable that hasn’t been declared, it declares it as AnyType. In general, it’s good programming practice to always declare variables. Get in the habit of declaring variables so that the compiler can catch typing mistakes. If you declare a siCounter variable at the form and accidently type liCounter, the compiler will catch it when Program | Compiler Warnings is turned on (you must be in the ObjectPAL editor). Declaring variables also makes your code easier to read.
Is it a good idea to declare all variables as AnyType so that you don’t have to worry about variable type? No. Avoid using AnyType variables because they slow your code down. Declaring a variable as an AnyType variable is the same as not declaring the variable at all. If you explicitly declare all your variables, your code will run faster. AnyType variables are useful when you don’t know what the data type will be.
This example demonstrates that declaring variables makes your code faster. In this example, you will create two buttons. Both buttons will beep, count to 2,000, and beep again. You will notice that the button with the declared counter variable will have a shorter duration between the beeps.
Change your working directory to Paradox’s Samples directory and create a new form with two buttons on it. Label them Undeclared and Declared.
Add lines 3–5 to the pushButton event of the Undeclared button.
1: ;DATATYPE :: btnUndeclared :: pushButton
2: method pushButton(var eventInfo Event)
3: beep()
4: for Counter from 1 to 2000 endFor
5: beep()
6: endMethod
Add lines 3–9 to the pushButton event of the Declared button.
1: ;DATATYPE :: btnDeclared :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: Counter SmallInt
5: endVar
6:
7: beep()
8: for Counter from 1 to 2000 endFor
9: beep()
10: endMethod
Check the syntax, save the form as DATATYPE.FSL, and run the form. Click both buttons. Note how much closer together the beeps are for the button labeled Declared.
Compile-Time Binding
Take a closer look at the preceding example, for two reasons: to explain compile-time binding and to review scope.
When the compiler compiles your source code, it casts all the variable types of the variables you have used. This process is called binding. When the compiler comes across a variable, it needs to find out whether the variable is declared. It first searches the method...endMethod structure in which the variable is used. Then, the compiler searches for a var...endVar structure above the current method...endMethod structure. After this, it searches the Var window of the object to which the code is attached, and then the Var window of the Object’s container, and so on until it reaches the form. When the compiler comes across the first occurrence of the variable declaration, it stops searching. If the compiler goes all the way up to the Var window of the form and the variable is not declared anywhere, then the variable is declared as AnyType.
Nearly all methods and procedures return something. In many cases, they return a simple LogicalTrue or False. You can use this return value to determine whether a method or procedure succeeded. For example, the following line of code displays True in the status bar if it can take the form out of Edit mode, and False if it can’t:
1: message(endEdit())
This simple example shows you that almost every method and procedure returns something. In most cases, you can use this fact to add error checking to your routines. For example, the following line of code displays the error box, complete with an error message when the locate() method returns False:
1: if not fldName.locate("Name", "Sam Unsicker") then
2: errorShow()
3: endIf
A variable declared as Logical can contain only two values: True or False. It can, however, be at any one of four states/values: unassigned, blank, True, or False. For example, type the following code on the pushButton event of a button:
1: ;Button :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: l Logical ;Declare l as a logical variable.
5: endVar
6:
7: l.view("Unassigned") ;View l.
8:
9: l = blank() ;Set l to blank.
10: l.view("Blank") ;View l.
11:
12: l = True ;Set l to True.
13: l.view("True") ;View l.
14:
15: l = False ;Set l to False.
16: l.view("False") ;View l.
17: endMethod
A field that is either blank or zero is considered null. Whenever you store an empty field, Paradox and dBASE actually store a null. This condition for a field is called blank in Paradox. The ObjectPAL Logical data type is equivalent to the dBASE and Paradox Logical field types.
In your ObjectPAL code, you can use Yes/No or On/Off in place of True/False, but the value that is displayed to the user is still dependent on the display format, usually True or False. If you type the following code on the pushButton event of a button, you’ll notice that, although l is specifically set to Yes in line 7, True is displayed when line 8 displays a Logical view box:
1: ;Button :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: l Logical
5: endVar
6:
7: l = Yes
8: l.view()
9: endMethod
Why use a Logical data type when you could just as easily use a String? One reason is that a Logical variable occupies only 1 byte of storage, and in computer languages, smaller is faster.
The following example shows that most methods and procedures return a value. You can return a value to a Logical variable and then display the value of the variable.
Using IsFile() Example
Suppose that you wish to find out whether a file exists. In this example, you use the fact that isFile() returns a Logical to determine wether a file exists.
On The Net: http://prestwood.com/forums/paradox/books/official/files/DATATYPE.FSL.
Create a form and add a button to it. Label the button Using a Logical.
Add lines 3–8 to the pushButton event of the Using a Logical button. Line 4 declares l as a Logical variable. isFile() returns True if the file exists and False if it doesn't. In line 7, l is given this return value from isFile(). Line 8 displays the result. Note that you passed view() a title to display.
Check the syntax, save the form as ISFILE.FSL, and run it. Click the button. The Logical viewer will display True or False, depending on whether the file exists.
Often, you need to calculate and convert numbers, such as time, date, currency, and so on. You either manipulate them or convert them so that you can manipulate them. For example, a Number is different from a String. When you have number characters stored in a String variable or Alpha field, you need to convert the string to a Number before you can use it in a calculation. ObjectPAL offers many methods and procedures for accomplishing this task. When declaring a variable as a number, you actually have three choices: Number, LongInt, and SmallInt.
A Number variable is the most flexible of the three number data types and takes up the most room in memory. A Number variable can contain up to 18 significant digits, and the power of 10 can range from ±3.4 * 10
A LongInt (long integer) variable can range from --2,147,483,648 to +2,147,483,647. Why use a data type that is limited by design when you have more powerful data types? Very simply, LongInt variables occupy only 4 bytes of storage, and in computer terms, smaller means faster.
A SmallInt (small integer) variable can range from --32,768 to +32,767. Again, the reason you may want to use a data type that is limited by design when you have more powerful LongInt and Number data types is because a SmallInt variable occupies only 2 bytes of storage, which is half of what LongInt variables take up. A SmallInt uses up the least amount of bytes to store small numbers. In computer terms, smallest is best.
As introduced earlier, whenever ObjectPAL comes across a number, it casts the number internally as the most appropriate type of number. If the number is an integer, ObjectPAL casts the number as either a small integer, a large integer, or a number, depending on its size, as the following code indicates. If the number contains a decimal point, it is cast as a Number. If you want, type the following in the pushButton event of a button:
1: msgStop("Datatype of 320", dataType(320))
This first example returns SmallInt, which takes up the smallest possible room in memory—only 2 bytes.
1: msgStop("Datatype of 33000", dataType(33000))
This second example returns LongInt because the data is too large to be a SmallInt.
1: msgStop("Datatype of 2200000000", dataType(2200000000))
This third example returns Number because the data is too large to be a LongInt.
1: msgStop("Datatype of 320.0", dataType(320.0))
This final example is rather interesting. Although the value clearly can fit into a SmallInt, the compiler puts it into a Number because of the decimal point.
Why is this concept so important? Doesn’t ObjectPAL take care of this for you? Well, yes and no. In most cases, you don’t have to worry about it. The following code passes a syntax check, but fails during runtime. You could pull your hair out for hours trying to figure out why. Type lines 3–8 into the pushButton event of a button and see whether you can figure out how to solve the problem. Following is the answer:
1: ;Button :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: si SmallInt
5: endVar
6:
7: si = 60 * 24 * 24
8: si.view()
9: endMethod
When ObjectPAL comes to line 7, it casts all the numbers in the expression as small integers. When the code is executed and the three numbers are multiplied, they result in a number larger than a small integer can handle, and a run-time overflow error occurs, as shown here:
Illustration 1
This problem has many solutions, all of which rely on the fact that ObjectPAL looks at an expression as a whole and sets the data types to the lowest common data type. You simply have to make any one of the elements involved with the calculation a larger number type (for example, LongInt or Number). Line 7 can be replaced with any of the following:
Solution 1
7: si = Number(60) * 24 * 24
The first solution casts the first number in the calculation as a Number. ObjectPAL internally casts the other two numbers, and the result is the same type.
Solution 2
7: si = 60.0 * 24 * 24
The second solution simply uses a floating point number (indicated by .0).
Solution 3
7: si = 60. * 24 * 24
The third solution is a variation of the second solution. ObjectPAL checks only for the existence of a decimal point in a number. It doesn’t check whether the number is a floating point number. The third solution uses this fact and simply adds a decimal point to the first of the three values.
Suppose that you want to convert a Number variable first to a SmallInt and then to a LongInt. This example demonstrates how to do that. Often when you are assigning one variable to another, you will have to cast one variable as the other variable’s type.
Change your working directory to the Paradox’s Samples directory. Open the DATATYPE.FSL form and put a button on it. Label the button Number to SmallInt & LongInt.
Add lines 3–18 to the pushButton event of the Number toSmallInt & LongInt button.
1: ;DATATYPE :: btnNumber :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: n Number ;Declare n as a number.
5: si SmallInt ;Declare si as a SmallInt.
6: li LongInt ;Declare li as a LongInt.
7: endVar
8:
9: n = 3.8 ;Set number to 3.8.
10: n.view() ;Displays 3.80.
11:
12: si = SmallInt(n) ;Set si to SmallInt of n.
13: si.view() ;Displays 3.
14:
15: li = LongInt(n) ;Set li to LongInt of n.
16: li.view() ;Displays 3.
17:
18: n.view() ;Displays 3.80.
19: endMethod
Check the syntax, run the form, and click the button.
The first viewer displays 3.80 in the Number viewer. Note that the number is formatted according to the Windows number format (the Windows default is two decimal points). When you click OK, the number is converted internally to a SmallInt variable and is displayed in a SmallInt viewer. Note that the number was chopped down to just 3 and not rounded up to 4. Click OK again. The number is again converted, this time to a LongInt. It’s displayed one last time to show you that the original nNumber variable is unchanged.
Whenever you need a number of a different type than is declared, cast it. In this example, you cast a number as both a long integer and a small integer, but you can go either way. Note, however, that converting a noninteger causes the decimal points to be stripped. For example, values of 1.1, 12.85, and .001 would be converted to 1, 12, and 0.
-4930 to 1.1 * ±104930, precise to six decimal places. The number of decimal places displayed depends on the user’s Control Panel settings. The value stored in a table does not, however. A table stores the full six decimal places.
Currency variables are rounded, not truncated. Although internally, ObjectPAL keeps track of Currency variables to six decimal places, the values are rounded when they’re displayed to the user. The number of decimal places depends on the user’s Control Panel settings. Most users use the default of two decimal points.
If you type in the following code, ObjectPAL shows you a dialog box with the correctly rounded figure of 19.96 in a currency view box.
1: ;DATATYPE :: btnPrice :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: nPrice Number ;Declare nPrice as a number.
5: endVar
6:
7: nPrice = 19.9599 ;Set nPrice to a number.
8: view(Currency(nPrice)) ;Displays 19.96.
9: endMethod
Although the number of decimal places depends on your Control Panel settings, the number will be rounded correctly. Also, note the alternative syntax used for view().
Although Time and Date data types aren’t related, each shares data and methods with the DateTime data type. When deciding between these three data types, ask yourself whether you need time in the same field as the date information. Whenever possible, separate date and time.
There are differences between dates stored in a table and Date variables. The Date data type can be any valid date, whereas a Paradox table can store only dates from 01/01/100 to 12/31/9999. Use the methods and procedures to bridge the gap and manipulate date data.
You can store any valid date with the ObjectPAL Date data type. For example, type the following code into the pushButton event of a button:
This displays 01/14/0001 in the status bar. This information isn’t very useful. Usually, you want the number of days or years between the two dates. The trick to getting the number of days between two dates is to cast the result as a number. For example, type the following code into the pushButton event of a button:
1: ;Button :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: dBorn Date
5: endVar
6:
7: dBorn = Date("01/20/1967")
8: dBorn.view("Enter your birthdate")
9: msgInfo("Days since your birthdate", Number(Today() - dBorn))
10: endMethod
A Time variable is stored in the format HH:MM:SS AM/PM. You could use any of the following as separators: blank, tab, space, comma, hyphen, slash, period, colon, or semicolon. If you want, type the following code into the pushButton event of a button. (All the following strings are legal time strings in ObjectPAL.)
1: ;DATATYPE :: btnTimeFormats :: pushButton
2: method pushButton(var eventInfo Event)
3: var t Time endVar
4: t = time("10:05:32 AM")
5: t.view()
6:
7: t = time("10;05;32 AM")
8: t.view()
9:
10: t = time("10 05 32 AM")
11: t.view()
12:
13: t = time("10,05,32 AM")
14: t.view()
15:
16: t = time("10/05/32 AM")
17: t.view()
18: endMethod
Note that although you can type time value strings in any of the legal formats of ObjectPAL, the time in the displayed view box is the same because it is guided by the operating system. It’s displayed to the user in the format specified in Control Panel. The values the user inputs must be in accordance with his or her Control Panel settings. Internally, ObjectPAL stores the time all the way down to the millisecond.
The user’s Control Panel settings determine whether the 12-hour time format (HH:MM:SS AM/PM) or the 24-hour (military) time format is used. Sometimes you want to convert 12-hour time to 24-hour time, however. The following routine casts a string as Time and then displays it in 24-hour format. You can type the code into the pushButton event of a button.
On The Net: http://prestwood.com/forums/paradox/books/official/files/WORLD-T.FSL
1: ;Button :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: s String
5: t Time
6: endVar
7:
8: formatSetTimeDefault("hh:mm:ss am")
9: s = "2:20:00 PM"
10: t = time(s)
11: formatAdd("24", "to(%h:%m:%s)na()np()")
12: formatSetTimeDefault("24")
13: t.view()
14: endMethod
Because there are many ways in ObjectPAL to accomplish a given task, you often need to test the speed of two routines. ObjectPAL offers, in the form of the time() method, an easy way to do this. To calculate the amount of time a scan loop takes on the CUSTOMER table, type in lines 3–19 into the pushButton event of a button:
1: ;DATATYPE :: btnSpeed :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: tcCustomer TCursor
5: tBeg, tEnd Time
6: nDifftime Number
7: endVar
8:
9: tcCustomer.open("CUSTOMER.DB") ;Open CUSTOMER.DB table.
10:
11: tBeg = time() ;Grab current time.
12: scan tcCustomer:
13: ;Nothing here. Just testing scan time.
14: endScan
15: tEnd = time() ;Grab current time.
16:
17: ;The following calculates the number of milliseconds that passed.
18: nDiffTime = number(tEnd) - number(tBeg)
19: nDiffTime.view("Milliseconds to scan Customer table")
20: endMethod
The preceding routine opens the CUSTOMER table, gets the current time, scans the CUSTOMER table, and then gets the current time again. Finally, it calculates the duration of the scan loop and displays it in milliseconds. Use the preceding technique whenever you need to know how fast a routine is in ObjectPAL—for example, when you discover two ways to accomplish the same task and need to determine which is faster.
is a special data type that stores both date and time values in the same variable. DateTime stores data in the form of hour-minute-second-millisecond year-month-day. The DateTime() method returns the current date and time. For example, to return the current date and time in a message information box, type line 3 into the pushButton event of a button.
As an interesting experiment, you can cast values with the procedure DateTime() and display the results. This experimenation helps you to understand the numbers behind the DateTime data type. For example, type line 3 into the pushButton event of a button:
Line 3 displays 12:00:00 AM, 00/00/0000 in a dialog box. This shows you that going back all the way to year 0 is legal in ObjectPAL. In fact, you can even use B.C. dates and times (negative numbers).
If you store data in an alphanumeric field type and later need to do a calculation on the value, you must cast the string in the field as a number. To cast a string as a number, use Number and DateTime() together. For example, type lines 3–8 into the pushButton event of a button:
The preceding line 8 displays the number 62042700600000 in a dialog box. This isn’t very useful, but you can use this code in a calculation with values (including another DateTime converted to a Number).
When you’re dealing with characters, your first choice should be the String data type. The String data type allows strings as long as 32,767 characters.
A quoted string typed into a method can’t be more than 255 characters long. You can, however, concatenate many strings together and display the result. For example, the following assigns values to a string variable that together add up to more than 255 characters and then displays it. If you want to participate in a typing exercise, type in lines 3–11 to the pushButton event of a button:
1: ;DATATYPE :: btnQuote :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: sNote String
5: endVar
6:
7: sNote = "The final stage in the evolution of a star,"
8: + " whose original mass was from 1.4 to 2.5 times greater then our Sun."
8: + " Gravitational collapse caused the star to contract to a sphere with "
8: + " a small radius of 10 to 20 kilometers, consisting mainly of free neutrons."
9: + " The density of a neutron star is a hundred million million times "
8: + "(10 to the 14) greater than the density of water."
10: + " Some rotating neutron stars evolve into pulsars."
11: msgInfo("Neutron Star", sNote)
12: endMethod
Use "" to represent an empty string. This is equivalent to the Null character. Alternatively, you can use the blank() method to empty a string. For example, type lines 3–10 into the pushButton event of a button:
1: ;Button :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: s String
5: endVar
6:
7: s = "Bradley Scott Unsicker"
8: s.view() ;Displays Bradley Scott Unsicker.
9: s.blank() ;Set s to null.
10: s.view() ;Displays null.
11: endMethod
Line 9 empties, or blanks out, the string. This is only one technique to empty a string. You also could replace line 6 with either of the following:
Sometimes you might want to code some special characters or backslash codes to add to a string when it’s displayed to the user. Special characters are ANSI characters not found on the keyboard. Backslash codes are the way to include some special characters in ObjectPAL. For example, \t represents a tab.
You use the string procedure chr() to display an ANSI code. Suppose that you’re going to display a message to the user and you want to embed a few returns (ANSI character 13) into the text, as shown here:
Illustration 2
On The Net: CHR.FSL demonstrates the technique of using chr(13).
You could use the following code:
1: ;Button :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: sThreat String
5: endVar
6:
7: sThreat = "You have violated the license agreement. Send me money."
8: msgInfo("Message from programmer", "Warning!" + chr(13) + chr(13) + sThreat)
9: endMethod
This technique can add a little extra flair to your applications that other programmers might not think to include.
A keycode is a code that represents a keyboard character in ObjectPAL scripts. A keycode can be an ASCII number, an IBM extended keycode number, or a string that represents a keyname known to Paradox.
Many programmers prefer to use the following Windows and C++ standard escapes:
When you need to work with a string longer then 32,767 characters, use a Memo variable. As soon as you set up a Memo variable, you can use writeToFile() and readFromFile() to set and retrieve data from a memo file. Memo fields can be as large as 512MB!
Three of the Memo type methods are of particular interest for manipulating a Memo variable: memo(), readFromFile(), and writeFromFile(). Following are descriptions of the three:
memo() ;Casts a value as a Memo
readFromFile() ;Reads a memo from a file
writeToFile() ;Writes a memo to a file
An array is a group or series of related items identified by a single name and referenced by an index. An array is a special type of variable. It enables you to store a group of elements identified by a single name. Arrays are stored in consecutive locations in memory. Think of each one of these locations as being a cell reserved for your data—very similar to a single-column Paradox table. If, for example, you declare an array with five elements, you can put a value directly in cell 3 and later pull it out. Following this analogy, an array is very similar to a one-field table. Arrays are limited to 65,535 elements.
There are two types of arrays: static and resizeable. A static array has a fixed number of elements and uses slightly less memory than a resizeable array. A resizeable array, however, can be resized. The size of a static array is set when you declare the array, and a resizeable array is set in code after you declare it. The following is the syntax model to declare a static array:
arrayName Array[size] dataType
For example, the following declares a three-element array ready to store three small integers:
1: var
2: arNumbers Array[3] SmallInt
3: endVar
As soon as the array is declared, you can reference any of the elements of the array directly:
arrayName
[element] = value
For example, the following sets the second and third elements in arNumbers to 10 and 20:
1: arNumbers[2] = 10
2: arNumbers[3] = 20
As soon as the elements of the array have values, you can retrieve the values much like you retrieve values from regular variables and fields. For example, the following displays the second element in the array in the status bar:
1: message(arNumbers[2])
If you think that using an array is just like using multiple variables, you’re right. One benefit of using arrays is that they group multiple variables into a single group of variables called an array. This gives you a way to address a set of values. For example, type the following piece of code into the pushButton event of a button. It uses a three-element array to store a user’s first, middle, and last name. Then it displays all three variables with a single line of code.
1: ;Button :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: arName Array[3] String ;Declare a 3 element fixed array.
5: endVar
6:
7: arName[1] = "Lester" ;Set first element of array.
8: arName[2] = "Earl" ;Set second element of array.
9: arName[3] = "Prestwood" ;Set third element of array.
10:
11: arName.view() ;View array.
12: endMethod
If you don’t know the size of the array needed at declaration, use a resizeable array. The following is the syntax model to declare an array variable:
arrayName Array[] DataType
After you declare a resizeable array, use the setSize() method to set its size. As soon as the array is declared and its size is set, you can reference any of the elements of the array the same way you reference the elements of a static array. For example, type the following code into the pushButton event of a button. It declares a resizeable array, sets its size to 10, and fills it with numbers:
Note that line 14 uses the addLast() method to add one additional element to the array before displaying it.
The following example shows you how to loop through an array using a for loop with the size() method. This technique is important because an array can’t use a forEach loop. (A forEach loop applies only to dynamic arrays.)
On The Net: http://prestwood.com/forums/paradox/books/official/files/ARRAY.FSL demonstrates using sets of information.
Suppose that you want to use an array with the enumFormNames() procedure to check for an open form. If the form is not open, display all the open forms in an array view box.
On The Net: http://prestwood.com/forums/paradox/books/official/files/ARRAYS.FSL.
Change your working directory to Paradox’s Samples directory and create a new form. Place a button labeled Check for open form on it.
In the pushButton event of the button, type lines 3–8. Line 4 declares a resizeable array—arForms—that is populated in line 8 with enumFormNames(). Note that you don't have to set the size of the resizeable array ahead of time. Line 5 declares a SmallInt variable named siCounter. Line 10 uses siCounter to scan through the array from the first element to however many elements are in the array. This is determined by arForms.size(). Line 11 checks to see whether the current array element is Arrays. If it is, a message information box is displayed in line 12, and execution stops in line 13 with the keyword return. If the element is not Arrays, the loop continues. This cycle continues until either Arrays is found or the end of the array is reached. If the routine is never exited, line 17 tells the user that the form isn't open. Line 18 shows the user all the currently open forms.
1: ;Button :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: arForms Array[] String
5: siCounter SmallInt
6: endVar
7:
8: enumFormNames(arForms)
9:
10: for siCounter from 1 to arForms.size()
11: if arForms[siCounter] = "Arrays" then
12: msgInfo("Found", "Form 'Arrays' is open")
13: return
14: endIf
15: endFor
16:
17: msgInfo("Not Found", "Form 'Arrays' is not open")
18: arForms.view("Current forms open")
19: endMethod
Check the syntax, save the form as ARRAY.FSL, and run it. Click the button. A dialog box indicates whether the form is open. After you click OK, the routine either stops or displays all the open forms. If a form with the name Arrays is open, the routine stops. Otherwise, an array view box displays all the open forms.
A DynArray (dynamic array) is very similar to a regular array, except that it uses address names rather than index numbers. Just as static and resizeable arrays are for using sets of data, so is the DynArray. If you can imagine an array is similar to a single-column Paradox table, then a DynArray is similar to a two-column Paradox table with the firt column being the primary key. The number of elements (indexes) you can have in a DynArray is limited only by memory. An index can be up to 255 characters and is sorted in alphabetical order.
Of the many DynArray methods, the following are some of the most useful:
contains()
Searches a DynArray for a pattern of characters.
empty()
Removes all items from a DynArray.
getKeys()
Loads a resizable array with the indexes of an existing DynArray.
removeItem()
Deletes a specified item from a DynArray.
size()
Returns the number of elements in a DynArray.
The following is the syntax model to declare a DynArray:
DynArrayName DynArray[] dataType
The following declares dyn1 as a DynArray:
1: var
2: dyn1 DynArray[] SmallInt
3: endVar
As soon as the array is declared, you can reference any of the elements of the DynArray directly:
DynArrayName[ElementName] = value
The following creates two elements in dyn1 and sets their values:
As soon as the elements of a DynArray have values, you can retrieve the values much like you retrieve values from a regular array. Rather than using a numbered index, however, you use named indexes. For example, the following displays the First_Nameelement in the array in the status bar:
1: message(myDynArray["First_Name"])
Now look at a completed example. Type lines 3–10 in the pushButton event of button.
The forEach structure enables you to loop through a DynArray, much like scan enables you to loop through a table. The syntax for forEach is as follows:
forEach
VarName in DynArrayName
Statements endForEach
For example:
1: forEach sElement in Dyn1 ;Scan through DynArray for sElement.
2: message(DynArrayVar[sElement]) ;Display element on status bar.
3: sleep(500) ;Wait for .5 seconds.
4: endForEach ;End for loop.
Suppose that you want to use a forEach loop to move through a DynArray of your system information, showing the user each element until you get to the IconHeight element. In addition to the forEach loop, this example also acquaints you with sysInfo(). Add lines 3–15 to the pushButton event of a button.
Check the syntax, run the form, and click the button. A message information dialog box appears for each element of the DynArray. Rather than cycle through all 30 bits of information, the program stops after the most important information is displayed. At the end of this parade of system information, a DynArray view box shows you all the elements at once.
In step 2, line 4 declares the DynArraydynSys. Note that no size is indicated in line 3 or in the body of the code. Line 5 declares sElement as a string variable. In line 8, the sysInfo() procedure is used to put information about your system into the DynArray. s is used in line 10 to store the index name, which is used in lines 11 and 12. Lines 10–13 comprise the forEach block, which moves through each element in the DynArray, checking to see whether the element name is equal to IconHeight. If it is equal, the loop is exited with the keyword quitLoop. If the loop hasn’t reached IconHeight yet, the element name and value are displayed. Finally, line 15 shows the complete DynArray all at once. In this case, a view is a better way to see all the elements of the array than looping through and showing one element at a time.
This simple example merely shows you how to loop through a DynArray and acquaints you with sysInfo(). Normally, you will do much more than this example does, using or manipulating each element as you go.
A custom data type is a way for you to create your own data type. You do this in a Type structure in either the Type window or in the event, much like you declare variables and constants. That is, the scope and instance of a custom type follows the same rules as a var block. The following is the syntax model:
type
UserType = ExistingType endType
The old Currency type of Paradox tables is now called Money. However, ObjectPAL was left to the same old Currency data type. If you don’t like this inconsistency, then you could declare a new variable type called Money and use it in place of Currency.
1: Type ;Begin type block.
2: Money = Currency ;Set custom types.
3: endType ;End type block.
After you do this, you could use either Currency or Money whenever you deal with money. You could use either line 2 or line 6 in the following code (lines 1–3 and 5–7 declare Total as a Currency data type).
1: var
2: Total Currency
3: endVar
1: var
2: Total Money
3: endVar
Another use of the type block is to define a data type as a set or record of existing types. For example, enter lines 3–23 into the pushButton event of a button:
1: ;Button :: pushButton
2: method pushButton(var eventInfo Event)
3: type ;Start type block.
4: ctEmployee = Record ;Start record block.
5: Name String ;Set elements of record.
6: Position String ;type employee.
7: YearEmp SmallInt
8: SickDays SmallInt
9: VacDays SmallInt
10: endRecord ;End record block.
11: endType ;End type block.
12:
13: var ;Begin var block.
14: emp ctEmployee ;Set emp to employee.
15: endVar ;End var block.
16:
17: emp.Name = "Glenn Allen Unsicker"
18: emp.Position = "Registered Nurse"
19: emp.YearEmp = 34
20: emp.SickDays = 312
21: emp.VacDays = 72
22:
23: emp.view() ;View emp record.
24: endMethod
As soon as a custom type is defined as a record, you can deal with a set of variables all at once.