Power - Chapter 14, Using Table and TCursor Variables (Corel Paradox - ObjectPAL Coding)
Power - Chapter 14, Using Table and TCursor Variables (Corel Paradox - ObjectPAL Coding)
► KB
tblCustomer.attach(CUSTOMER)
msgInfo("Number of Customers", tblCustomer.cCount("Customer
No"))
endMethod
Using Table Methods and Procedures
Many of the table methods deal with data in the table and are duplicated in the TCursor class.
After you declare a Table variable and open it with attach(), you can use the Table methods on
it. The following statements, for example, are valid:
1: tbl.attach("DEST.DB")
2: tbl.cMax("Total")
3: tbl.delete()
The next example puts the cAverage() method to work with a Table variable.
Using cAverage() with a Table Variable
Suppose that you want to find the average sale for all invoices in the Customer table. You can
do this by using cAverage() and a Table variable.
Step By Step
1. Change your working directory to Paradox’s Samples directory. Open a form or create a new one. Place a button
labeled Average Order on it.
2. Alter the pushButton event of the button as follows:
1: ;Button :: pushButton
2: method pushButton(var eventInfo Event)
3: const
4: ORDERS = ":work:Orders.db"
5: endConst
6: var
7: tblOrders Table
8: endVar
9:
10: tblOrders.attach(ORDERS)
11: msgInfo("Average Order", tblOrders.cAverage("Total
Invoice"))
12: endMethod
3. Check the syntax, save the form as TableExamples.fsl, and change the mode to View Data. Click the button. After a
short time, the answer appears onscreen:
Illustration 1
Compacting dBASE and Paradox tables
Deleted records are not immediately removed from a dBASE table. Instead, they are flagged as
deleted and kept in the table. In addition, if you delete records from a Paradox table, they
cannot be retrieved. However, the table file and associated index files contain dead space
where the record was originally stored. To compact a Paradox table, alter the pushButton event
of a button as follows:
 1. method pushButton(var eventInfo Event)
 2. const
 3. ORDERS = ":work:Orders.db"
 4. endConst
 5. var
 6. tblOrders Table
 7. regIndex Logical
 8. endVar
Â
 9. tblOrders.attach(ORDERS)
10. try
11. regIndex = True
12. if not tblOrders.compact(regIndex) then errorShow() endIf
13. onFail
14. ;There probably is a lock on the table.
15. errorShow()
16. endTry
17. endMethod
For dBASE tables, the optional argument regIndex specifies whether to regenerate or update
the indexes associated with the table. When regIndex is set to True, this method regenerates
all indexes associated with the table. If you use compact() with a Paradox table, all indexes are
regenerated and dead space is removed. This method fails if any locks have been placed on the
table or the table is open. This method returns True if successful; otherwise, it returns False.
Creating Tables
You can use the Create keyword to create a table. The following example demonstrates
creating a Paradox table:
1. method pushButton(var eventInfo Event)
2. const
3. CUSTOMERNOTES = ":work:CustomerNotes.db"
4. endConst
5. var
6. tblCustomerNotes Table
7. tvCustomerNotes TableView
8. endVar
9. if isTable(CUSTOMERNOTES) then
10. if msgQuestion("Confirm", "Table exists. Overwrite it?") <>
"Yes" then
11. return
12. endIf
13. endIf
14. errorTrapOnWarnings(True)
1: tc.FieldName = Today()
In this example, quotation marks aren’t used around the field name in the table. Quotation
marks aren’t needed for field names that have no special characters. If, however, a field
name contains a special character, such as a space or a hyphen, quotation marks are required.
For the sake of consistency, you might put quotation marks around all field names when you
use a TCursor, as in the following example:
1: tc."FieldName" = Today()
A TCursor works in the background. Therefore, when you manipulate a database, movement
through the table doesn’t appear onscreen. Because the screen isn’t refreshed, changes
are made quickly.
Using a TCursor
Treat a TCursor variable like other variables. Declare it in the Var window of the object. If the
method executes only once (like pushButton), or if you need a private version of the variable,
declare it within the method.
In general, opening and closing a TCursor can be time-consuming because opening and closing
a file on a disk is slower than leaving it open. Therefore, it’s best to minimize the number of
times you open and close these objects. If the method you use occurs once, such as
pushButton, it’s okay to declare it inside the method:
1: ;Button :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: tc TCursor
5: endVar
6:
7: tc.open("Customer.db")
8: msgInfo("Current Customer", tc."Name")
9: tc.close()
10: endMethod
Referring to Fields in a Table with a TCursor
ObjectPAL offers three ways to use a TCursor to refer to fields in a table: without quotes, with
quotes, and dereferencing. For example:
1: tc.Last_Name ;Without quotes.
2: tc."Last_Name" ;With quotes (allows special characters).
3: tc.(2) ;Dereferencing with parentheses.
Line 1 above refers to the field with just the field name of the field as it appears in the table. If
you have a field with spaces in it—for example, Last Name—then you cannot use this first
technique. Line 2 above surrounds the field name with quotes and works with all field names.
The preferred usage is to always use quotation marks, because it will always work. Line 3 above
shows how to dereference a field by surrounding it with parentheses. Line 3 above is referring
to the second field in the table. You could also dereference a field by using a variable. For
example:
1: var
2: sField String ;Declare a variable.
3: tcCustomer TCursor
4: endVar
5: tcCustomer.open("Customer.db")
6: sField = "Name" ;Assign a field name to the variable.
7: view(tcCustomer.(sField)) ;Dereference the variable using
parentheses.
Dereferencing with parenthesis is a general ObjectPAL concept and is used in other places in
ObjectPAL. For example, dereferencing is used with UIObjects. You can store the name of a
UIObject in a variable and use it as part of your dot notation listing the object path. For
example, if you name a page of a form pge1 and place a box on it named box1, you can do the
following:
1: var
2: sObject String ;Declare a variable.
3: endVar
4:
5: sObject = "box1" ;Assign an object name to the variable.
6: pge1.(sObject).color = Red ;Dereference the variable using
parenthesis.
Example of Using a TCursor to Refer to a field
Suppose that you want to find a row based on one field and then display a different field from
that record. This next example uses the Biolife table to demonstrate moveToRecord() and
locate().
Step By Step
1. Set your working directory to Paradox’s Samples directory. Create a new form, place a drop-down field and a
button labeled Display Species on it:
Illustration 2
2. Name the drop-down field fldCommonName.
3. Alter the open event of the list object of the drop-down field as follows (use the Object Tree to get to the list object
of the drop-down field).
method open(var eventInfo Event)
DoDefault
Self.datasource = "[:work:biolife.\"Common Name\"]"
endMethod
4. Alter the pushButton event of the button as follows:
;Button :: pushButton
method pushButton(var eventInfo Event)
const
BIOLIFE = ":work:biolife.db"
endConst
var
tcBiolife TCursor
endVar
if fldCommonName.isBlank() then
msgStop("Error", "First select a common name.")
return
endIf
Illustration 3
Inserting a Record with a TCursor
With a TCursor, you can manipulate and add data directly to a table with no interaction on the
form, just as you can use a UIObject to put the table connected to it into Edit mode, insert a
record, and post a value. Suppose that you want to insert a new record into the Customer
table. To do this, open a TCursor to the Customer table and insert a new record. You can do the
same tasks with a TCursor, as the following example demonstrates.
Step By Step
1. Set your working directory to Paradox’s Samples directory. Open the TCursorExamples.FSL form you created in
the last example and add a button labeled Add your Name.
2. Alter the pushButton event as follows:
1: ;Button :: pushButton
2: method pushButton(var eventInfo Event)
3: const
4: CUSTOMER = ":work:Customer.db"
5: endConst
6: var
7: tcCustomer TCursor
8: sName String
9: tvCustomer TableView
10: endVar
11:
12: tcCustomer.open(CUSTOMER)
13: tcCustomer.edit()
14: tcCustomer.insertRecord()
15:
16: ; Enter a new customer name.
17: sName = ""
18: sName.view("Enter your name")
19: if sName = "" then
20: beep()
21: message("No name entered. Aborted")
22: tcCustomer.cancelEdit()
23: return
24: endIf
25: tcCustomer.Name = sName
26:
27: ; Generate a new customer number.
28: try
29: tcCustomer."Customer No" = tcCustomer.cMax("Customer
No") + 1
30: onFail
31: sleep(1000)
32: retry
33: endTry
34:
35: ; Post new record.
36: tcCustomer.postRecord()
37: tcCustomer.endEdit()
38: tcCustomer.close()
39:
40: ;View table.
41: tvCustomer.open(CUSTOMER)
42: tvCustomer.action(MoveEnd)
43: tvCustomer.action(MoveScrollLeft)
44: tvCustomer.action(MoveScrollLeft)
45: tvCustomer.action(MoveBeginLine)
46: endMethod
3. Check the syntax, save the form, run the form, and click the button. Nothing seems to happen. Open the Customer
table. Now the first record is 100, and it displays your name.
Illustration 5
Redeclaring TCursors
The TCursor class has both an open() and a close() method. It generally is considered good
programming practice to close any TCursor you open. Any TCursor you leave open will use up
resources. If you open a TCursor, should you close() it before reusing it? Although it is generally
a good habit to get into, it is not always necessary. Look at the following code:
1: var
2: tc TCursor
3: endVar
4:
5: tc.open(t1)
6: tc.open(t2)
In this simple example, a TCursor is declared and used twice in a row, without ever closing the
first TCursor. The question is, "Does the first instance of the tc variable close when you reopen
it?" Yes.
Now take a look at the following example. The following code is in a Var window at the form
level.
1: ; Var Window of form
1: var
2: tc TCursor
3: endVar
This code is in the pushButton event of a button on the form.
1: ; Button1 :: pushButton
4: tc.open(t1)
The question is, should you close the TCursor with tc.close() after using it? The answer is, it
depends. You could leave the TCursor open just in case you’re going to use it again. This
would save the time needed to reopen it. If, however, you are only going to use the TCursor
once, then you should close it to save resources.
Using setRange()
setRange() specifies a range of values (contrasted with setGenFilter(), which provides true filters—discussed next).
setRange() is always preferred over setGenFilter(), because setRange() uses the active index. This makes setRange() faster
than setGenFilter().
Suppose that you want to allow the user to specify a range of records they want to
see—similar to a live query. The technique presented in this example uses setRange() on a
TCursor with the resync() method.
Step By Step
1. Change your working directory to Paradox’s Samples directory and create a new form with the Customer table
in the data model and displayed in a table frame. Add two buttons labeled All Cities and Set Range of Cities. Finally,
add two fields named fldStart and fldEnd, as shown here:
Illustration 6
2. Restructure the Customer table and add a secondary index called City (see Figure 14-3 for the settings).
Figure 3: Add this City index to the Customer table
3. Alter the pushButton event of the Set Range of Cities button as follows:
1: ;btnRange :: pushButton
2: method pushButton(var eventInfo Event)
3: var
4: tcCustomer TCursor
5: endVar
Illustration 7
Using setGenFilter()
Using setGenFilter() requires two steps. First you declare a DynArray variable and populate it
with the filtering data, and then you pass the DynArray to setGenFilter(). After you declare a
DynArray, you assign values to it specifying the field and the values. Following are some
examples of the types of formulas you can use with setGenFilter():
1: var
2: dyn DynArray[] String
3: endVar
4:
5: dyn["State"] = "CA" ;State field equals 'CA'.
6: dyn["Total"] = "< 0" ;Negative numbers in Total field.
7: dyn["Total"] = "> 100, < 1000" ;Greater then 100 & less then
1000.
8: dyn["Total"] = ">= 4, <= 8"
For example, to view all orders with a Balance Due over $100.00 and less than $1,000.00, enter
the following on the pushButton event of a button on a form bound to the Orders table.
1: ;btnShowMiddle :: pushButton
2: pushButton (var eventInfo Event)
3: var
4: dyn DynArray[] String ;Declare DynArray.
5: endVar
6:
7: dyn["Balance Due"] = "> 100, <1000" ;Assign filter to it.
8: ORDERS.setGenFilter(dyn) ;Use it with setGenFilter().
Tip: You can speed up a TCursor by using update(), setBatchOn(), or copyToArray(). If you use
setBatchOn(), make sure to follow it with setBatchOff() every time you use it because it
places an exclusive lock.
Autoincrementing
So far, this chapter has only touched on the power and capabilities of the Table and TCursor
variables. A whole book could be devoted to just these two variable types. This final section of
this chapter addresses autoincrementing with the TCursor.
In this section, you learn how to autoincrement using ObjectPAL. First, you autoincrement a
simple field. Second, you autoincrement a nonkey field. Third, for the most elegant solution,
you add locking to the routine. By studying simple and elegant methods, you learn how to
implement different routines under different situations and functional programming.
Autoincrementing a field involves inserting a new record, finding the highest value, adding 1 to
it, and storing the new value. You already know how to insert a new record, as in the following:
1: active.insertRecord()
2: Line_Item.insertRecord()
3: self.action(DataInsertRecord)
4: tc.insertRecord()
To get the current highest value, either move to the end of the table and put the value in a
variable, or use the cMax() method. Either way, after you get the highest value, you need to
put it into a variable.
Autoincrementing and Locking
Now you have just one more loophole to close. Theoretically, it’s still possible for two users
to end up with the same number. You can use autoincrementing with locks to make sure that
this doesn’t happen. A lock is a feature of the BDE that prevents other users from viewing,
changing, or locking a table or a record while one user has a lock on it. The next example uses
autoincrementing with locks.
Example of Autoincrementing with Locks
Suppose that you want to autoincrement a field in a multiuser environment. To do this, you
need to work with locks.
Step By Step
1. Set your working directory to Paradox’s Samples directory. Create a new form with the Customer table in the
data model:
Illustration 8
2. Create a table called incremnt.db (see Figure 14-4 for the structure).
Figure 4: The incremnt table
3. Open the incremnt table and add one row to it with the current highest Customer No value from the Customer
table. This should be 9,841 unless you’ve altered the data in the table.
4. Add lines 3 and 4 to the Var window of the page. Lines 3 and 4 declare a TCursor and SmallInt variables for use in
the action event.
1: ;Page :: Var
2: Var
3: tc TCursor
4: siCounter SmallInt
5: endVar
5. Alter the action event of the page as follows:
1: ;Page :: action
2: method action(var eventInfo ActionEvent)
3: if eventInfo.id() = DataInsertRecord then
4: if not tc.open("incremnt.db") then errorShow() endIf
5: siCounter = 0
6: while not tc.lock("Full")
7: siCounter = siCounter + 1
8: message("Attempting to establish lock: " +
String(siCounter))
9: sleep(1000)
10: if siCounter = 10 then
11: DisableDefault
12: msgStop("Warning", "Could not establish lock.")
13: return
14: endIf
15: endWhile
16: edit()
17: DoDefault
18: tc.edit()
19: tc."Customer No" = tc."Customer No" + 1
20: tc.postRecord()
21: Customer_No = tc."Customer No"
22: tc.unLock("Full")
23: Name.moveTo()
24: tc.close()
25: endIf
26: endMethod
6. Check the syntax, save the form as Auto3.fsl, and run the form. Insert a record.
Summary
In this chapter, you learned about ObjectPAL’s Table and TCursor objects. You learned they differ in fundamental ways.
Table objects give you a handle to a table and, in general, its methods and procedures deal with the table as a whole. A
TCursor object gives you a handle to a specific record (or row) in a table and, in general, its methods and procedures deal
with the data inside the table. When utilizing TCursor and Table variables, remember that you are utilizing another channel
to the database. Table variables can lock out regular users by putting write and exclusive locks on the table. When
programming a TCursor, think of the open TCursor as another user and code accordingly.
Comments
0 Comments.
Share a thought or comment...
Paragraph
p Words: 0
Share
Visit Profile