Académique Documents
Professionnel Documents
Culture Documents
Stonefield Systems Group Inc. 2055 Albert Street, Suite 420 Regina, SK Canada S4P 2T8 Phone: (306) 586-3341 Fax: (306) 586-5080 CompuServe: 75156,2326 Email: dhennig@stonefield.com World Wide Web: www.stonefield.com Overview
In FoxPro 2.x, developers edited records using
m e m v a r . The Introduction
s c a t t e rm e m v a r , editing the
g a t h e r
purpose of this indirect editing of fields was to protect the record by buffering it. With Visual FoxPro, data buffering is built in, so fields can be edited directly. This session will discuss how data buffering works and explores strategies for selecting which buffering mechanism to use and how to handle multi-user conflicts.
If youve spent any time at all working with VFP, one of the things youve probably learned is that while you can continue to do things the old way if you wish, VFP provides you with a better way to perform the same task. How records are edited in forms is a perfect example of this. Heres the old way I used to write code to edit a record in a data entry screen: The screen has g e tobjects for memory variables with the same names as the tables fields (for example, M.CUST_ID and M.NAME). When the user positions the table to a particular record (for example, using the Next button), use record to the memory variables and s h o wg e t sto refresh the values shown on the screen. The user cannot edit the variables (theyre either disabled or have w h e n clauses that evaluate to .F.) because the user is currently in view mode.
When the user chooses the Edit button, try to lock the record; display an appropriate message if we cant. Check the value of each field against its memory variableif they dont match, another user must have edited and saved the record since we first displayed it. In that case, display an appropriate message and use s c a t t e rm e m v a rand s h o wg e t sagain so the user sees the current contents of the record. If the fields and memory variables match, either enable the evaluate to .T. so the user can edit the variables.
g e tobjects or make
their
w h e nclauses
When the user chooses the Save button, do some validation to ensure everything was entered according to our rules, then g a t h e rm e m v a rto update the record from the memory variables and unlock the record. Disable the g e tobjects or make their w h e nclauses evaluate to .F. so the user is once again in view mode. Notice in this scheme we dont do a direct r e a dagainst the record. Instead, we allow the user to edit memory variables and only write those memory variables back to the record if everything went OK. The reason for using this method is it protects the table; we dont allow any data to be stored unless it passes all the rules. Also notice the record is locked while the user is editing the memory variables. This prevents another user from editing the same record at the same time. It does, however, suffer from the out to lunch syndromeif the user starts the edit, then goes for lunch, the record stays locked, unavailable to other users for editing. This isnt the only way to edit records, of course. You could do the lock just before saving the record instead of when the edit mode starts. This minimizes the time the record is locked, allowing other users access to it. This has its own drawback, though: if the user edits the variables and clicks on Save, what happens if some other user edited the record in the meantime? Do you overwrite their changes? Do you prevent the record from being saved? This is a design issue you must handle on a case-by-case basis. The whole purpose of all this effort is to protect the data. If you were writing an application that only you would ever
www.dfpug.de/loseblattsammlung/migration/whitepapers/Buffer.htm 1/11
7/9/13
use, youd probably make it a lot simplerjust r e a dagainst the fields in the record directly. This makes the screen act like the form equivalent of a b r o w s e , since everything you type goes directly into the record. However, since we cant trust those pesky users to know what they can and cant enter, we have to protect the data by building a firewall between the user and the table. Creating this firewall in FoxPro 2.x took a significant amount of coding. VFP provides a built-in firewall mechanism that gives us the best of both worlds: direct r e a dagainst a record while only permitting the data to be written after it passes all the tests. This mechanism is buffering.
Buffering
Using memory variables to hold the contents of a record can be considered like creating a buffer of data. The data is transferred from the record to the buffer by using s c a t t e rm e m v a rand from the buffer to the record with
g a t h e rm e m v a r .
Not only can VFP do this type of single record buffering (called record or row buffering) automatically, it also supports another type of buffering (called table buffering) in which multiple records are accessed through a buffer. Record buffering is normally used when you want to access or update single records at a time. This is common in data entry mechanisms like that described above: the user can display or edit a single record in the form. Table buffering would be the choice for updating several records at a time. A common example of this is an invoice header-detail screen. By using table buffering for the invoice detail table, you can allow the user to edit detail lines as long as they wish, and then save or cancel all the detail records at once. In addition to two buffering mechanisms, there are two locking mechanisms. The old way I described earlier can be considered to be a pessimistic locking schemethe record is locked as soon as the user chooses Edit, and stays locked until they choose Save. This ensures no one else can change the record while this user is doing so, which may or may not be a good thing, depending on your application. The other method I described earlier is an optimistic locking mechanismthe record is only locked for the brief amount of time it takes to write the record, and is immediately unlocked. This maximizes the availability of the record (this is also known as maximizing concurrency) but means we have to handle conflicts that occur if two users edit the record at the same time. As well see in a moment, this is actually easy to do in VFP, so optimistic buffering will probably be the mechanism of choice for most applications. Since records can be automatically buffered, theres no longer a need to use the manual buffer mechanism. In other words, now we can r e a ddirectly against the fields in the record and not worry about maintaining memory variables for each one. To save the changes, we simply tell VFP to write the buffer to the table, and to cancel the changes, we tell it not to. Well see how to do that in a moment. VFP implements buffering by creating a cursor whenever a table is opened. The cursor is used to define properties for the table. In the case of local tables, the only property for the cursor is how buffering is performed; views and remote tables have additional properties beyond the scope of this session. These properties are set using the c u r s o r s e t p r o p ( )function and examined with c u r s o r g e t p r o p ( ) . Well see the use of these functions shortly. Table buffering has an interesting implementation regarding appended records: as records are added to the buffer, theyre assigned a negative record number. r e c n o ( )returns -1 for the first appended record, -2 for the second, and so on. You can use g owith a negative number to position the buffer to the appropriate appended record. This has an implication for routines handling record numbersinstead of testing for b e t w e e n ( l n R e c n o ,1 , r e c c o u n t ( ) )to ensure lnRecno is a valid record number, youll now have to test for b e t w e e n ( l n R e c n o , 1 ,r e c c o u n t ( ) )o rl n R e c n o<0 .
Using Buffering
Buffering is turned off by default, so VFP acts just like FoxPro 2.x in terms of how updates are written to a table. To use buffering, you must specifically turn it on. Buffering is available for both free tables and those attached to a database. Buffering requires that you s e tm u l t i l o c k so nsince by default it too is set off; youll get an error message if you forget to do this. You can put m u l t i l o c k s=o nin your CONFIG.FPW or use the Options function under the Tools pad to save this setting as the default. Buffering is controlled using c u r s o r s e t p r o p ( ' B u f f e r i n g ' ,< n > ,< A l i a s > ) . You dont have to specify < A l i a s >if youre setting buffering for the current table. < n >is one of the following values depending on the buffering and locking method you wish to use:
Buffering/Locking Method <n> no buffering record, pessimistic record, optimistic table, pessimistic 1 2 3 4
2/11
www.dfpug.de/loseblattsammlung/migration/whitepapers/Buffer.htm
7/9/13
table, optimistic
For example, to enable optimistic record buffering, use what buffering is currently in use for a table, use
To enable buffering in a form, you could specify c u r s o r s e t p r o p ( )for each table in the forms Load method, but the preferred approach is to set the forms BufferMode property to either optimistic or pessimistic (the default is none). The form will then automatically use table buffering for tables bound to grids and row buffering for all other tables. If you use a DataEnvironment for the form, you can override the forms BufferMode for a particular table by setting its BufferModeOverride property as desired. While the user is changing the data in the buffered record (theyre in the middle of editing the record), you have access to not only the value theyve entered into each field, but also the former value of each field and its current value (the value actually on disk). Two new functions, o l d v a l ( )and c u r v a l ( ) , were added for this purpose. Heres how you obtain the appropriate values:
To Get: the value the user entered (the value in the buffer) the value before the user changed anything
the current value in the record
Use:
You may be wondering how the value returned by c u r v a l ( )would differ from the one returned by o l d v a l ( ) . It obviously wouldnt if only a single user is running the application. However, on a network and with optimistic locking, its possible that after the user started editing the record, another user edited the same record and saved their changes. Heres an example: Bob brings up record #2 in CONTACTS.DBF and clicks on the Edit button:
Field
o n e sJ o n e s LAST_NAME J i l l B i l l FIRST_NAME B
Bob changes the first name to Sam but doesnt save the record yet:
Field
o n e sJ o n e s LAST_NAME J a m FIRST_NAME S B i l l
Mary brings up record #2 in CONTACTS.DBF, clicks on the Edit button, changes the first name to Eric, and saves. At Bills machine:
Field
o n e sJ o n e s LAST_NAME J a m FIRST_NAME S B i l l
Notice F I R S T _ N A M E ,o l d v a l ( ' F I R S T _ N A M E ' ) , and c u r v a l ( ' F I R S T _ N A M E ' )all return different values. By having access to the original value, the buffered value, and the current value for each field in a record, you can: determine which fields the user changed by comparing the buffered value to the original value; and detect whether other users on a network made changes to the same record after the edit had started by comparing the original value to the current value. If you dont care about the old and current values but only wish to detect if a field was edited by the user, use
www.dfpug.de/loseblattsammlung/migration/whitepapers/Buffer.htm 3/11
7/9/13
g e t f l d s t a t e ( ) . This new function returns a numeric value changed. g e t f l d s t a t e ( )is called as follows: if something about the current recor
Value Description 1 2 3 4 No change The field was edited or the deletion status of the record was changed A record was appended but the field was not edited and the deletion status was not changed. A record was appended and the field was edited or the deletion status of the record was changed.
Changing the deletion status means either deleting or recalling the record. Note deleting and then immediately recalling the record will result in a value of 2 or 4 even though theres no net effect to the record. If you dont specify an alias or workarea, g e t f l d s t a t e ( )operates on the current table. Specify 0 for < F i e l d N u m b e r >to return the append or deletion status of the current record. If you specify -1 for < F i e l d N u m b e r > , the function will return a character string with the first digit representing the table status and one digit for the status of each field. In the example mentioned earlier, where Bill edits the second field, g e t f l d s t a t e ( 1 )would return 112. The first digit indicates the record was not appended or deleted, the second that the first field was unchanged, and the third that the second field was changed.
buffer was successfully written to the record. If the record buffer hasnt changed (the user didnt edit any fields, add a record, or change the deleted status for the record), t a b l e u p d a t e ( )returns .T. but actually does nothing.
t a b l e u p d a t e ( ) can take
Handling Errors
Continuing on with the Bill and Mary example, the code executed when Bill clicks the Save button uses the t a b l e u p d a t e ( )function to try to write the buffer to the record. Remember Mary edited the record and saved her changes as Bill was editing the same record. When Bill clicks on Save, t a b l e u p d a t e ( )will return .F., meaning it didnt write the buffer. Why? VFP will not write the buffer to the record under the following conditions: Another user changed and saved the record while this user was editing it (as happened in this example). VFP automatically compares o l d v a l ( )and c u r v a l ( )for each field. If it detects any differences, we have a conflict.
www.dfpug.de/loseblattsammlung/migration/whitepapers/Buffer.htm 4/11
7/9/13
The user entered a duplicate primary or candidate key value. A field or table rule was violated, or a field that doesnt support null values is null. A trigger failed. Another user has the record locked. This can be minimized by avoiding manually locking records with same buffer locking mechanism for a table in all forms and programs that access it. Another user deleted the record. You must decide what to do when
Next or Previous buttons while editing a record and those functions dont issue a t a b l e u p d a t e ( ) , you must handle the error that will occur when the automatic save is attempted. In both of these cases, the proper place to handle this is in an error trapping routine. Error handling has been improved in VFP. The old way to set an error trap (which you can still use in VFP) is to use the o ne r r o rcommand to specify a procedure to execute when an error occurs. This error routine would typically look at
VFP now provides an automatic error handling mechanism: the Error method. If an Error method exists for an object or form, it will automatically be executed when an error occurs without having to manually set the trap. a e r r o r ( ) is a new function that helps in figuring out what went wrong. You pass it an array name and it creates or updates the array with the following elements:
Element Type 1 2 3 4 5 6 7 Numeric Character Character Numeric or Character Numeric or Character Numeric or Character Numeric
e r r o r ( ) ). m e s s a g e ( ) ).
The error parameter (for example, a field name) if the error has one (same as s y s ( 2 0 1 8 ) ) or .NULL. if not. The work area in which the error occurred if appropriate, .NULL. otherwise. The trigger that failed (1 for insert, 2 for update, or 3 for delete) if a trigger failed (error 1539), or .NULL. if not. .NULL. (used for OLE and ODBC errors). .NULL. (used for OLE errors).
For example,
a e r r o r ( l a E R R O R )will create
Here are the common errors that may occur when VFP attempts to write the buffer to the table:
Error # Error Message 109 1539 1581 1582 1583 1585 1884 Record is in use by another Trigger failed Field does not accept null values Field validation rule is violated Record validation rule is violated Record has been modified by another Uniqueness of index violated
Comment Check element 5 to determine which trigger failed. Check element 3 to determine which field was involved. Check element 3 to determine which field was involved.
Handling most of these errors is straightforward: tell the user the problem and leave them in edit mode to correct the problem or cancel. For error #1585 (record has been modified by another), there are several ways you could handle the error:
You could tell them someone else modified the record and then cancel their edits using too thrilled by this approach <g>.
5/11
7/9/13
You can force the update of the record by using t a b l e u p d a t e ( . F . ,. T . ) . This causes the other users changes to be overwritten by the current users. This user might be happy, but the other user probably wont be. You can display the changes the other user made to the record in another copy of the same form (easy to do with VFPs ability to create multiple instances of the same form). The user can then decide whether the changes the other user made should be kept or not, and you can either use t a b l e u p d a t e ( . F . ,
. T . )to force
the update or
t a b l e r e v e r t ( )to cancel.
A more intelligent scheme involves determining if we have a real conflict or not. By real, I mean: did both users change the same field or not. If the fields they updated are different, we could tell VFP to just update the field this user changed, leaving the other users changes intact. An example might be in an order processing system. One user may have edited the description of a product while another user entered an order for the product, thereby decreasing the quantity on hand. These changes arent mutually exclusiveif we make our table update less granular (that is, we dont update an entire record at a time, just the fields we changed), we can satisfy both users. Heres the logic of how that works: Find a field where
another user. If the fields buffered value is the same as o l d v a l ( ) , this user didnt change the field, so we can prevent overwriting its new value by setting the buffered value to c u r v a l ( ) . Find a field where the buffered value is different than o l d v a l ( ) . This is a field this user edited. If o l d v a l ( )equals c u r v a l ( ) , the other user didnt change this field, so we can safely overwrite it. If we find a field where the buffered value is different than o l d v a l ( )but the same as the same change. While this may seem unlikely, one example would be when someone sends a change of address notice to a company and somehow two users decide to update the record at the same time. Since the changes were identical, we might be able to overwrite the field. However, in the case of a quantity being updated by the same amount (for example, two orders for the same quantity were entered at the same time), you wouldnt want to overwrite the field, and would consider this to be a real conflict.
If we have a case where the buffered value of a field is different than both o l d v a l ( )and same either, both users changed the same field but to different values. In this case, we have a real conflict. You have to decide how to handle the conflict.
In the case of inventory quantity on hand or account balances, one possibility is to apply the same change the other user made to the buffered value. For example, if o l d v a l ( )is 10 and c u r v a l ( )is 20, the other user increased the amount by 10. If the buffered value is 5, this user is decreasing the amount by 5. The new buffered value should therefore be v a l u e+ c u r v a l ( )-o l d v a l ( ) , or 15. In the case of Date fields, business rules and common sense might help. For example, in a patient scheduling program with a field containing the date of a patients next visit, the earlier of the two dates in conflict is probably the correct one to use, unless its prior to the current date, in which case the later date is the correct one. Other types of fields, especially Character and Memo fields, often cant be resolved without asking the user to make a decision about overwriting the other users changes or abandoning their own. Allowing the user to see the other users changes (as mentioned earlier) can help them make this decision. Heres some code that will do this type of conflict resolution (this code assumes weve already determined the problem is error #1585, the record had been modified by another user):
7/9/13
d oc a s e *A n o t h e ru s e re d i t e dt h i sf i e l db u tt h i su s e rd i d n ' t ,s og r a bt h e *n e wv a l u e . c a s el l O t h e r U s e ra n dn o tl l T h i s U s e r r e p l a c e( l c F i e l d )w i t hc u r v a l ( l c F i e l d ) *A n o t h e ru s e rd i d n ' te d i tt h i sf i e l d ,o rt h e yb o t hm a d et h es a m e *c h a n g e ,s ow ed o n ' tn e e dt od oa n y t h i n g . c a s en o tl l O t h e r U s e ro rl l S a m e C h a n g e *U h o h ,b o t hu s e r sc h a n g e dt h i sf i e l d ,b u tt od i f f e r e n tv a l u e s . o t h e r w i s e l l C o n f l i c t=. T . e n d c a s e n e x tl n I *I fw eh a v eac o n f l i c t ,h a n d l ei t . i fl l C o n f l i c t l n C h o i c e=m e s s a g e b o x ( ' A n o t h e ru s e ra l s oc h a n g e dt h i s'+; ' r e c o r d .D oy o uw a n tt oo v e r w r i t et h e i rc h a n g e s( Y e s ) ,'+; ' n o to v e r w r i t eb u ts e et h e i rc h a n g e s( N o ) ,o rc a n c e l'+; ' y o u rc h a n g e s( C a n c e l ) ? ' ,3+1 6 ,' P r o b l e mS a v i n gR e c o r d ! ' ) d oc a s e *O v e r w r i t et h e i rc h a n g e s . c a s el n C h o i c e=6 =t a b l e u p d a t e ( . F . ,. T . ) *S e et h ec h a n g e s :b r i n gu pa n o t h e ri n s t a n c eo ft h ef o r m . c a s el n C h o i c e=7 d of o r mM Y F O R Mn a m eo N a m e *C a n c e lt h ec h a n g e s . o t h e r w i s e =t a b l e r e v e r t ( ) e n d c a s e *N oc o n f l i c t ,s of o r c et h eu p d a t e . e l s e =t a b l e u p d a t e ( . F . ,. T . ) e n d i fl l C o n f l i c t
*F i n dt h ef i r s tm o d i f i e dr e c o r d ,t h e np r o c e s se a c ho n ew ef i n d . l n C h a n g e d=g e t n e x t m o d i f i e d ( 0 ) d ow h i l el n C h a n g e d< >0 *M o v et ot h er e c o r da n dt r yt ol o c ki t . g ol n C h a n g e d i fr l o c k ( ) *C h e c ke v e r yf i e l dt os e ew h i c ho n e sh a v eac o n f l i c t . l l C o n f l i c t=. F . f o rl n I=1t of c o u n t ( ) l c F i e l d =f i e l d ( l n I ) l l O t h e r U s e r =o l d v a l ( l c F i e l d ) < >c u r v a l ( l c F i e l d ) l l T h i s U s e r =e v a l u a t e ( l c F i e l d )< >o l d v a l ( l c F i e l d )
www.dfpug.de/loseblattsammlung/migration/whitepapers/Buffer.htm 7/11
7/9/13
l l S a m e C h a n g e=e v a l u a t e ( l c F i e l d )= =c u r v a l ( l c F i e l d ) d oc a s e *A n o t h e ru s e re d i t e dt h i sf i e l db u tt h i su s e rd i d n ' t ,s og r a bt h e *n e wv a l u e . c a s el l O t h e r U s e ra n dn o tl l T h i s U s e r r e p l a c e( l c F i e l d )w i t hc u r v a l ( l c F i e l d ) *A n o t h e ru s e rd i d n ' te d i tt h i sf i e l d ,o rt h e yb o t hm a d et h es a m e *c h a n g e ,s ow ed o n ' tn e e dt od oa n y t h i n g . c a s en o tl l O t h e r U s e ro rl l S a m e C h a n g e *U h o h ,b o t hu s e r sc h a n g e dt h i sf i e l d ,b u tt od i f f e r e n tv a l u e s . o t h e r w i s e l l C o n f l i c t=. T . e n d c a s e n e x tl n I *I fw eh a v eac o n f l i c t ,h a n d l ei t .I fw ed o n ' t ,u n l i k et h er o wb u f f e r i n g *c a s e ,w ed o n ' td oa n y t h i n gn o ws i n c ea l lr e c o r d sw i l lb ew r i t t e nl a t e r . i fl l C o n f l i c t l n C h o i c e=m e s s a g e b o x ( ' A n o t h e ru s e ra l s oc h a n g e d'+; ' r e c o r d'+l t r i m ( s t r ( l n C h a n g e d ) )+' .D oy o uw a n tt o'+; ' o v e r w r i t et h e i rc h a n g e s( Y e s ) ,n o to v e r w r i t eb u ts e e'+; ' t h e i rc h a n g e s( N o ) ,o rc a n c e ly o u rc h a n g e s( C a n c e l ) ? ' ,3+1 6 ,; ' P r o b l e mS a v i n gR e c o r d ! ' ) d oc a s e *O v e r w r i t et h e i rc h a n g e s :w ed o n ' ta c t u a l l yn e e dt od oa n y t h i n gb e c a u s ew e ' l l *d ot h e ma l ll a t e r( t h i sc a s ei so n l yh e r ef o rc l a r i t y ) . c a s el n C h o i c e=6 *S e et h ec h a n g e s :b r i n gu pa n o t h e ri n s t a n c eo ft h ef o r m . c a s el n C h o i c e=7 d of o r mM Y F O R Mn a m eo N a m e *C a n c e lt h ec h a n g e si nt h i sr e c o r do n l y . o t h e r w i s e =t a b l e r e v e r t ( ) u n l o c kr e c o r dl n C h a n g e d e n d c a s e e n d i fl l C o n f l i c t *W ec o u l d n ' tl o c kt h er e c o r d ,s oc a n c e lt h ec h a n g e st ot h i sr e c o r do n l y . e l s e =m e s s a g e b o x ( " S o r r y ,w ec o u l d n ' ts a v er e c o r d# "+l t r i m ( s t r ( l n C h a n g e d ) ) ) =t a b l e r e v e r t ( ) u n l o c kr e c o r dl n C h a n g e d e n d i fr l o c k ( ) *F i n dt h en e x tm o d i f i e dr e c o r da n dp r o c e s si t . l n C h a n g e d=g e t n e x t m o d i f i e d ( l n C h a n g e d ) e n d d ow h i l el n C h a n g e d< >0 *S i n c ew er e v e r t e da n yc h a n g e sw h e r ew ef o u n dac o n f l i c ta n dt h eu s e rw a n t e d *t oc a n c e lt h e i ro w nc h a n g e s ,l e t ' sf o r c et h er e m a i n d e ro ft h eu p d a t e s . =t a b l e u p d a t e ( . T . ,. T . )
If a table has changes in a table buffer that havent been written out to disk and you attempt to close the table or change the buffering mode, youll get an error (#1545): Table buffer for alias <Alias> contains uncommitted changes.
Transactions
As weve seen, table buffering is a convenient way to buffer a number of changes to a table and then write or abandon those changes all at once. However, theres one flaw with this approachwhat happens if one of the records in the buffer is locked or has been edited by another user? In this case, t a b l e u p d a t e ( . T . )will return .F. and the error trapping routine can be called. The problem: some records were saved and some werent. Now you have a fairly complicated mess on your hands if you need to back out those changes already made. Heres an example of such a problem: you go to the bank to transfer money from your savings account to your checking account. The account update program reduces your savings account balance by the appropriate amount, then tries to increase your checking account balance by the same amount. The program might look something like this:
s e e kM . A C C O U N T 1
www.dfpug.de/loseblattsammlung/migration/whitepapers/Buffer.htm 8/11
7/9/13
t r a n s a c t i o ncommand. Any table changes after this command has been issued, even those made with t a b l e u p d a t e ( ) , are not written to the disk until an e n dt r a n s a c t i o ncommand is encountered. Think of a
transaction as a buffers buffer. The transaction is held until you determine all changes could be made successfully and issue an e n dt r a n s a c t i o n . If the program crashes or the computer is rebooted before e n d
Lets look at the bank update example but this time use a transaction as a wrapper for the update:
Records automatically locked by VFP during a transaction are automatically unlocked when the transaction is complete. Any locks you set manually are not automatically unlocked; you are responsible for unlocking those records yourself. If you use u n l o c kduring a transaction, the record actually stays locked until the
www.dfpug.de/loseblattsammlung/migration/whitepapers/Buffer.htm 9/11
7/9/13
transaction is done, at which time all specified records are unlocked. Although transactions give you as much protection as they can, its still possible a hardware failure or server crash during the e n dt r a n s a c t i o ndisk writes could cause data to be lost. Transactions only apply to local tables. Transactions for remote tables are controlled using the
d ow h i l e
b e g i nt r a n s a c t i o n i ft a b l e u p d a t e ( . T . ) e n dt r a n s a c t i o n e l s e r o l l b a c k d oE R R O R _ R O U T I N E e n d i ft a b l e u p d a t e ( . T . ) p r o c e d u r eE R R O R _ R O U T I N E *D os e t u ps t u f fh e r e ,i n c l u d i n gc h e c k i n gw h a th a p p e n e d .I fw ef o u n de r r o r *# 1 5 8 5 ,d ot h ef o l l o w i n gc o d e . l n C h a n g e d=g e t n e x t m o d i f i e d ( 0 ) d ow h i l el n C h a n g e d< >0 . . . e n d d ow h i l el n C h a n g e d< >0 *S i n c ew er e v e r t e da n yc h a n g e sw h e r ew ef o u n dac o n f l i c ta n dt h eu s e rw a n t e d *t oc a n c e lt h e i ro w nc h a n g e s ,l e t ' sf o r c et h er e m a i n d e ro ft h eu p d a t e sa n d *t h e nu n l o c ka l lt h er e c o r d sw em a n u a l l yl o c k e d . b e g i nt r a n s a c t i o n i ft a b l e u p d a t e ( . T . ,. T . ) e n dt r a n s a c t i o n *S o m eo t h e re r r o ro c c u r r e dn o w ,s or o l l b a c kt h ec h a n g e sa n dd i s p l a ya n *a p p r o p r i a t ee r r o rm e s s a g e( y o uc o u l da l s ot r yt oh a n d l ei th e r ei fy o u *w i s h ) . e l s e =a e r r o r ( l a E r r o r ) r o l l b a c k =m e s s a g e b o x ( ' E r r o r# '+l t r i m ( s t r ( l a E r r o r [ 1 ] ) )+' :'+l a E r r o r [ 2 ]+; 'o c c u r r e dw h i l es a v i n g . ' ) e n d i ft a b l e u p d a t e ( . T . ,. T . )
Other Issues
As we saw earlier, g e t f l d s t a t e ( )can be used to determine if anything in the current record has changed. This allows you to create forms that no longer need a edit mode; the data in the form is always available for editing. Each fields InteractiveChange event could enable the Save and Cancel buttons only if the user actually changed something using code similar to:
7/9/13
g e t f l d s t a t e ( )using code
www.dfpug.de/loseblattsammlung/migration/whitepapers/Buffer.htm
11/11