Posted 16 years ago on 6/7/2008 and updated 1/26/2009
Take Away:
Using loop structures in ObjectPAL (for, forEach, scan, while, and looping with timers).
KB101170
In programming languages, control structures are keywords used to alter the sequence of execution and add logic to your code. All programming languages contain control structures. They are a fundamental element of any programming language.
Normally, a programming language executes line after line in sequential order. This is true whether the language is a line-oriented language such as BASIC or a statement-oriented language such as C. A branch transfers program control to an instruction other than the next sequential instruction.
A set of instructions that is repeated a predetermined number of times or until a specified condition is met is called a loop. Control structures enable you to branch and loop based on whether a certain condition evaluates to True. They also enable you to add logic to your code. The techniques you learn in this chapter will help you when you learn other programming languages. Although the syntax might vary, the control structures in ObjectPAL apply to other languages.
In programming languages, a control structureis a set of keywords used to branch or loop. With control structures, you can alter the sequence of execution and add logic to your code. A loop is a set of instructions that is repeated a predetermined number of times or until a specified condition is met.
A Quick Example:
For example, lines 6 and 7 in the code that follows are repeated 10 times as indicated in line 5.
1: var ;Begin variable block. 2: x Number ;Declare x as a number. 3: endVar ;End variable block. 4: 5: for x from 1 to 10 ;Begin loop. 6: message(x) ;Display x in status bar. 7: sleep(500) ;Wait second. 8: endFor ;End Loop.
When you program, you often need to repeat a number of commands. One of the most important aspects of a programming language is its capability to loop-that is, to go through a series of commands a certain number of times. ObjectPAL has four ways to loop: scan, for, forEach, and while.
Using the loop and quitLoop Keywords
Because all four loops have two keywords in common, loop and quitLoop, this section addresses these before getting into the four loops. Loop passes program control to the top of the nearest loop. When loop is executed within a for, forEach, scan, or while structure, it skips the statements between it and endFor, endForEach, endScan, or endWhile and returns to the beginning of the structure.
quitLoop exits a loop to the nearest endFor, endForEach, endScan, etc. quitLoop immediately exits to the nearest for, forEach, endScan, or endWhile keyword. Execution continues with the statement that follows the nearest endFor, endForEach, endScan, or endWhile. quitLoop causes an error if it is executed outside a for, forEach, scan, or while structure.
The whileLoop
A while loop enables you to execute a set of commands while a condition is true. Specifically, while repeats a sequence of statements as long as a specified condition evaluates to True. At the end of the set of commands, the condition is tested again. In a typical while loop, you must make the condition False somewhere in the set of commands.
While Condition ;Test condition, if true [Statements] ;execute these statements. ;Don't forget to make ;the condition false some place. endWhile ;Go back to while.
While starts by evaluating the Logical expression Condition. If Condition evaluates to False, the execution continues after endWhile. If Condition is True, Statements are executed. When the program comes to endWhile, it returns to the first line of the loop, which reevaluates Condition. Statements are repeated until Condition evaluates to False.
Using a whileLoop Example
This example demonstrates a simple while loop that counts to 10. It uses a counter variable to keep track of how many times to cycle through the loop.
Create a new form with a button labeled Whileloop.
Add lines 3-14 to the pushButton event of the While loopbutton. Line 4 declares Counter as a SmallInt variable. In line 7, Counter is initialized to 0. (All variables must be initialized before you use them.) Lines 9-13 form the while loop. When Counter is less than 10, lines 10-12 are executed. In line 12, the Counter variable is incremented. If you leave this line out, the counter will loop forever. (You'll have to use ctrl+break to exit the loop.) With while loops, you have to make the condition False at some point. This example uses a simple Counter variable that eventually makes the condition in line 9 False, which in turn ends the loop. Execution continues after endWhile.
1: ;LOOPS :: Button :: pushButton 2: method pushButton(var eventInfo Event) 3: var 4: Counter SmallInt ;Counter is a SmallInt. 5: endVar 6: 7: Counter = 0 ;Variables must be initialized.
8: While Counter < 10 ;Start of while loop. 9: message(Counter) ;Display Counter. 10: sleep(250) ;Sleep for second. 11: Counter = Counter + 1 ;Increments counter. 12: endWhile ;End of while loop.
13: message("Final value for Counter is " + StrVal(Counter)) 14: endMethod
Check the syntax, save the form as LOOPS.FSL, and run the form. Click the While Loop button. Watch the status bar count from 0 to 9 in the loop and display the number 10 after the loop is finished.
This example uses a while loop as a counter. Normally, you would use a for loop for a loop that has a counter. While loops are useful for executing a set of commands as long as a condition is True. For example, you might execute a set of commands while the left character of a string is equal to Sm or while a box's color is red. The important thing to remember about a while loop is that it's your responsibility to make the condition False to exit the loop.
The forLoop
Use a for loop to execute a sequence of statements a specific number of times. You don't have to increment the counter in a for loop, as you must in a while loop.
For Counter [from startVal] [to endVal] [step stepVal] Statements endFor
The three values startVal, endVal, and stepVal are values or expressions that represent the beginning, ending, and increment values. For executes a sequence of statements as many times as the counter specifies. The specific number of times is stored in Counter, and it is controlled by the optional from, to, and step keywords. You can use any combination of these keywords to specify the number of times that the statements in the loop should be executed. You don't have to declare Counter explicitly, but a for loop runs faster if you do. If you previously didn't assign a value to Counter, from creates the variable and assigns the value of startVal to it.
You can use for without the from, to, and step keywords. If startVal is omitted, the counter starts at the current value of Counter. If endVal is omitted, the for loop executes indefinitely-not too practical! Finally, if stepVal is omitted, the counter increments by 1 each time through the loop, as in the following example:
1: ;Button :: pushButton 2: for Counter from 1 to 3 ;Count from 1 to 3. 3: Counter.view() ;Execute these statements. 4: endFor ;End for block.
This section demonstrates two for loops: with and without step. The first for loop counts to 5 and the second for loop counts to 20 by .5.
Create a new form and place two buttons on the form and name one Simple for loop and the other For loop using step.
Add lines 3-9 to the pushButton event of the Simple forloop button. Line 4 declares Counter as a SmallInt variable. You also could use Number, LongInt, or AnyType. In fact, you could choose not to declare Counter at all, but that would be poor programming. Lines 6-8 make up the for loop. Line 6 sets up the Counter variable to count from 1 to 5 at the default increment step of 1. Between for and endFor, you put all the commands that you want to loop through five times. In this case, a message information dialog box is displayed. Line 8 ends the for loop. Remember that, in a for loop, all the commands between for and endFor are executed once every time the loop is executed. Line 9 displays the final value for Counter, which is always one increment beyond the CountTo value.
6: for Counter from 1 to 5 ;Start of for loop. 7: msgInfo("Counter", Counter) ;Commands to loop. 8: endFor ;End of for loop.
9: message("Note the final value for Counter is " + String(Counter)) 10: endMethod
Add lines 3-15 to the pushButton event of the For loopusing step button. Line 4 declares Counter and CountTo variables of type Number. Because of containership and encapsulation, you can use the same name (Counter) in both buttons. Line 7 initializes CountTo to 20, and line 8 displays it so that the user can either accept it or change it. Line 10 sets up the Counter variable to count from 0 to the value stored in CountTo at the increment step of .5. Lines 10-13 make up the for loop. Line 11 displays the Counter in the status bar, and line 12 sleeps for a quarter second. Line 15 reminds you that the Counter value is always one increment beyond the value used in the last loop.
1: ;LOOPS :: Button2 :: pushButton 2: method pushButton(var eventInfo Event) 3: var 4: Counter, CountTo Number 5: endVar 6: 7: CountTo = 20 8: CountTo.view("Enter a number") 9: 10: for Counter from 0 to CountTo Step .5 11: message(Counter) 12: sleep(250) 13: endFor 14: 15: message("Note the final value for Counter after loop is " + String(Counter)) 16: endMethod
Check the syntax, save the form, and run it. Click the Simplefor loop and For loop using step buttons. Watch the status bar as they loop.
Use a For Loop to Loop Array Elements
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.)
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.
forEachLoop
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:
forEachVarName 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.
Using the scan Loop
You bought Paradox so that you could view, enter, and manipulate data. For the ObjectPAL programmer, the scan loop is very easy to use, and it's a powerful tool for manipulating the data in a table. In many ways, using a scan loop is similar to using a query in Interactive mode. In fact, there are occasions when using a scan loop is faster than doing a complicated query.
The scan loop is an extremely powerful tool that you can use to manipulate a whole table or, with the for keyword, a subset of a table. The scan loop scans a TCursor and executes ObjectPAL instructions. The syntax for a scan loop is as follows:
The colon at the end of the first line is required. It separates the first line from the following statements. Scan starts with the first record in a TCursor and moves from record to record, executing Statements for every record. Scan automatically moves from record to record through the table, so you don't need to call action(DataNextRecord) within your statements. When an indexed field is changed by a scan loop, the changed record moves to its sorted position in the table. Therefore, it's possible to encounter the same record more than once. As with all the loops discussed in this chapter, be sure to put statements that occur only once before the loop.
The for expression is used to filter the records. Only the records that match the expression are acted on. All other records are skipped. When you use the for keyword with scan, it must be followed by a colon to differentiate it from a for loop.
Using a scan Loop to Make a Field Proper
Suppose that you want to use a scan loop to change a field to a proper format. This example uses a scan loop and the format() command to change a field named Name in the CUSTOMER.DB table to a proper format. Before you make drastic changes to a table, you should always make a backup copy. This section acquaints you with the script, the scan loop, and format().
Step By Step
Make the Paradox's Samples directory your working directory. Open the CUSTOMER.DB file. Change some of the last names to all uppercase or all lowercase.
Choose File | New | Script and type in lines 3-16. Line 4 declares tc as a TCursor variable. Line 7 asks for permission to continue using a message question box. If the answer is Yes, then line 8 opens the TCursor and puts it in Edit mode in line 9. In this code, the scan loop uses only three lines: lines 10-12. Line 13 ends the edit session, and line 14 closes the TCursor. Line 15 tells the user that the program is done.
1: ;Script :: Run 2: method run(var eventInfo Event) 3: Var 4: tc TCursor 5: endVar 6: 7: if msgQuestion("Question?", "Make Name field proper?") = "Yes" then 8: tc.open("CUSTOMER.DB") 9: tc.edit()
Check the syntax, save the script as PROPER.SSL, and run the script. After the script has finished executing, reopen CUSTOMER.DB. Now the Name field is in a proper format.
Implementing Cascade Deletes with Looping
A cascade delete is a setting you can set with many database products. Cascade delete deletes all the child records of a parent record. Because Paradox doesn't support cascade deletes, you must delete the child records. In a 1:1 relationship, this isn't a big deal. Simply delete both records in each table, as in the following:
This technique works quite well. You just have to remember to do it.
In a 1:M relationship, deleting child records is trickier. You have to loop through the children and delete them one at a time. You shouldn't use a scan loop to delete records from a table. Instead, use either a while loop with eot() (end of table) or for loop with nRecords(). The following is an example using a for loop:
1: var 2: Counter Number 3: tc TCursor 4: endVar 5: 6: tc.attach(ChildUIObjectName) 7: tc.edit() 8: 9: for Counter from 1 to tc.nRecords() 10: tc.deleteRecord() 11: endFor
In this code, you attach the TCursor to the UIObject, which ensures that the TCursor will have the same restricted view that the object has. Therefore, tc.nRecords() returns the number of records in the restricted view-not the whole table.
Another technique is to use a while loop with eot(). The following code, for example, works great in versions 1.0 and 4.5:
1: method pushButton(var eventInfo Event) 2: var 3: tc TCursor 4: endVar 5: 6: errorTrapOnWarnings(Yes) 7: 8: tc.attach(LINEITEM);Attach to detail table. 9: tc.edit() 10: 11: ;Delete all children records. 12: while not tc.eot() 13: tc.deleteRecord() 14: endWhile 15: 16: edit() ;Make sure form is in edit mode. 17: Order_No.deleteRecord() ;Then delete the parent record. 18: endMethod
The preceding technique is not complete with version 5.0 and above of Paradox because of the interactive filter settings introduced with version 5.0. The following represents the preferred way to implement cascade delete in Paradox 9:
1: ;btnCascadeDelete :: pushButton 2: method pushButton(var eventInfo Event) 3: var 4: tc TCursor 5: endVar 6: 7: tc.attach(LINEITEM) ;Attach to detail table. 8: tc.dropGenFilter() ;Drop any user set filters. 9: tc.home() ;Put TCursor on first record. 10: tc.edit() 11: 12: while not tc.eot() ;If there are any child 13: tc.deleteRecord() ;records, delete all of them. 14: endWhile 15: 16: edit() ;Make sure form is in edit mode. 17: Order_No.deleteRecord() ;Delete the parent record. 18: endMethod
Why show you three different ways to accomplish the same task? For several reasons, first, to get you acquainted with the various ObjectPAL commands; and second, to show you that in ObjectPAL, there often are many ways to accomplish a single task. Which one is best? The best technique usually is the fastest or the one that uses the smallest amount of code. In this case, I believe all three are about equal.
Interrupting a Loop
Sometimes you may want to give the user the option of interrupting a loop. For example, if you are scanning through a large table, you could allow the user to abort the procedure in midstream. This type of control adds a touch of professionalism to your application. The following example demonstrates how you can enable the user to interrupt a loop by pressing the esc key.
Suppose that you want to loop to 1,000 and display the counter in the status bar as the loop increments. The twist on this example is that you need to enable the user to press esc to interrupt the loop. This example uses the form's prefilter with vChar() to trap a keystroke.
Step By Step
Create a new form and place a button labeled Count to 1000 and a text box on the form (see Figure 16-2).
Add line 3 to the Var window of the form.
1: ;Form :: Var 2: Var 3: lFlag Logical 4: endVar
Add lines 5-7 to the keyPhysical event of the form.
1: ;Form :: keyPhysical 2: method keyPhysical(var eventInfo KeyEvent) 3: if eventInfo.isPreFilter() then 4: ;This code executes for each object on the form 5: if eventInfo.vchar() = "VK_ESCAPE" then 6: lFlag = True 7: endIf 8: else 9: ;This code executes only for the form 10: endIf 11: endMethod
Alter the mouseDown event of the text box as follows:
Add lines 3-16 to the pushButton event of the Count to 1000 button. Line 4 declares a variable private to a method for use in the for loop (lines 9-16). Line 7 sets the flag to False in case the user presses esc, setting the flag to True. Line 10 displays the value of siCounter in the status bar. Line 11 sleeps for the minimum amount of cycles (about 52 milliseconds), which is plenty of time to yield to Windows. This enables the esc key to sneak in. Line 12 checks whether the flag has changed to True. If the flag is True, line 13 displays a message, and line 14 quits the loop.
1: ;Button :: pushButton 2: method pushButton(var eventInfo Event) 3: var 4: siCounter SmallInt 5: endVar 6: 7: lFlag = False 8: 9: for siCounter from 1 to 1000 10: message(siCounter) 11: sleep() 12: if lFlag = True then 13: message("Counting interrupted") 14: quitloop 15: endIf 16: endFor 17: endMethod
Check the syntax, run the form, and click the button. As the computer counts to 1,000, the counter is shown in the status bar. Press esc or click the text box to interrupt the loop (see Figure 16-3).
Note: Sometimes programmers use sleep(1) to indicate they want to release control to windows, but for the least amount of time. In place of sleep(1), you can use sleep() without a parameter-sleep(). In that case, Windows automatically permits about two events to occur.
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.
Example of Looping with Timers
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.
Demo Files: LoopUsingTimer.fsl.
Step By Step
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 63: 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 64: 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.
I'm trying to be able to interrupt a search loop using the Esc key and it doesn't seem to work.
I'm pasting the code below, where : idTC is a table containing about 28,000 names resTC is a table to capture the search results ResTBL is a TableFrame on the form to show the search results. Z_Abort is a global variable
the form's KeyPhysical method is:
method keyPhysical(var eventInfo KeyEvent)
if eventInfo.isPreFilter() then ;// This code executes for each object on the form If ( eventInfo.vChar() = "VK_ESCAPE" ) then Z_Abort = True endif
else ;// This code executes only for the form
endIf
endMethod
and the loop itself is the following : While idTC.locateNextPattern(SearchField, searchFor) resTC.InsertRecord() resTC.Nombres = idTC.Nombres resTC.Apellidos = idTC.Apellidos resTC.PostRecord() resTC.UnlockRecord() ResTBL.Resync(ResTC) ProgBar1.Pos = idTC.Recno() Sleep() If Z_Abort then QuitLoop endif endWhile
Any ideas on why this is not working? I'm using Paradox 10 (10.0.0.663), Windows XP Pro
Looping in objectPAL, I know it to understand loop is difficult but it is not possible for you because i start to share aussiessayservices.com/essaydot-com-review/ with you and also start to understand some of best style where you can easily understand loop which is interesting to take.