Logo

Hacker’s Guide to Visual FoxPro
An irreverent look at how Visual FoxPro really works. Tells you the inside scoop on every command, function, property, event and method of Visual FoxPro.

GETFLDSTATE(), SetFldState()

This is an interesting pair of functions. GETFLDSTATE() is essential to any sensible strategy for dealing with conflicts when saving data. SetFldState() is strange—both in how it’s supposed to behave and in how it actually behaves. They’re both relevant only when you’re dealing with buffered data.

Usage

uCodes = GetFldState( cField | nFieldCode
                      [, cAlias | nWorkarea ] )
lSuccess = SetFldState( cField | nFieldCode, nCode
                       [, cAlias | nWorkarea ] )

Parameter

Value

Meaning

cField

Character

The name of a field whose status is being queried or changed.

nFieldCode

-1

Query the status for all fields in the current record.

0

Query or change the deleted status of the current record. Maybe this is because the deleted mark can sort of be seen as the "zeroth" field of any table.

Positive

Query or change the nFieldCode-th field in the current record, based on the table's structure.

nCode

Numeric

The status to assign the specified field. See Help for a list.

cAlias

Character

The alias for the table whose status is being queried or changed.

Omitted

If nWorkArea is also omitted, query or change the table in the current work area.

nWorkArea

Numeric

The work area containing the table to be queried or changed.

Omitted

If cAlias is also omitted, query or change the table in the current work area.

uCodes

Numeric

Contains the status of a single field or the deleted status of the current record. See Help for meaning of status values.

1 – Untouched field
2 – Existing field edited or record deleted
3 – New record; field untouched
4 – New record, field edited, or record deleted

Character

Contains a string where the first character is the deleted status for the current record and each subsequent character is the changed status of a field. Fields are represented in definition order.

.NULL.

Returned by GetFldState() when the record pointer is at end-of-file.

lSuccess

.T.

The field's change status or the record's deletion status was changed.

.F.

Doesn't occur.

GETFLDSTATE() makes a lot of sense. It tells you whether a particular field has changed or if the record has been deleted. It also tells you whether a record is new. Most of the conflict resolution strategies we’ve seen involve using GETFLDSTATE() to figure out which fields have changed, so you can see if someone else changed the same fields. (Would that human conflict resolution were so simple.)

The behavior of GetFldState() when you add a new record and some fields have default values varies in different VFP versions and based on which command (INSERT or APPEND BLANK) you use to add the new record. In some combinations, the field state for a field default value is 4; in other combinations (all of which involve adding with INSERT), it's 3. While we generally think 4 is the right value, we can see the logic that says that APPEND BLANK actually changes the field, while INSERT doesn't. The key point is that you need to test to see what behavior your combination of VFP version and insertion mechanism produces, so your code can act accordingly. If your framework checks for changes in order to figure out things like whether certain buttons should be enabled or disabled, you may want to use SetFldState() to change the value for such a field to 3.

One warning here: GETFLDSTATE() doesn’t get changed for a field until you’ve left the control it’s bound to. If you make changes but don’t leave the field, causing its Valid to fire, the changes don’t get sent to the control’s ControlSource, and GETFLDSTATE() doesn’t see them. This is consistent with controls not getting their Value changed until Valid is fired. If you need to force the Valid, try This.SetFocus().

The docs say that SetFldState()changes the status of a field or record. That it does. However, what the docs don't say is that your changes matter only when you're dealing with a view. For tables, it doesn't matter how you set the field state. Changed fields will lead to updated records. For views, you can affect what gets saved back to the base table.

The original release of VFP 7 has a new bug that makes it hard to take advantage of GetFldState() and SetFldState(). In this version, GetFldState() always returns 4 for a field with a default value, even if you explicitly set it to 3. However, internally, VFP honors your change to the field state. (For views, when a field with a default value has field state set to 4, the default value is saved to the base table. When the field has a field state of 3, the view-level default value is not saved to the base table.) The problem this bug causes is that you can't easily determine whether a record has changes or not. This bug is fixed in VFP 7 Service Pack 1.

That release also has a bug involving GetFldState(). Under certain circumstances, when you add two records in a row, doing TableUpdate() inside a transaction after each, GetFldState() shows the second record as appended (that is, returns 3's rather than 1's). This bug is also fixed in VFP 7 Service Pack 1.

Example

* You can prevent an update from failing by checking for
* conflicts ahead of time. Loop through all the fields and check
* whether other users have changed any that you changed.
* This example assumes you're dealing only with existing records
* and handles only a single record. Wrap it with a loop
* involving GetNextModified() to handle multiple records.
FOR nCnt = 1 TO FCOUNT()
   * Did you change this field?
   IF GetFldState(nCnt)<>1
      * Did anyone else change it?
      IF CurVal(FIELD(nCnt))<>OldVal(FIELD(nCnt))
         * Conflict. You changed it and so did someone else.
         * Ask the user what to do.
         nNowWhat = MessageBox("Someone else changed "+ ;
                    FIELD(nCnt) + ", too. Save anyway?",
                    MB_YESNO+MB_ICONEXCLAMATION, ;
                    "Save Conflict")
         IF nNowWhat = IDNO
            * Grab the other user's value.
            REPLACE (FIELD(nCnt)) WITH CurVal(FIELD(nCnt))
         ENDIF
      ENDIF
   ENDIF
ENDFOR
* Now you're ready to actually update the thing.

See Also

Buffering, ControlSource, CurVal(), GetNextModified(), OldVal(), TableUpdate(), Valid