![]() |
|
Published 1998-10-01 Printer-friendly version
| Publisher's Note |
| At several points in this article, the template code is much too long to fit in a normal width document. We've used continuation characters ( the rule character | ) to show where the code is continued. This code will not work as shown in this article. You should use the enclosed templates instead. We hope this message helps eliminate the confusion that could be caused. |
We do a lot of survey work at my company, and I have to write a lot of programs for entering data. Sometimes it is important to ensure the accuracy of data entry, and in this case, having the data entered twice, and checking that the data entered both times is the same is useful.
The approach I took to this was to build on the HistoryField method that exists in the WindowManager class. This allows the user to press a History key and retrieve the previous value in the field. What we want to do is to save the first entry of the data into the History buffer, and check this against data entered into the regular file buffer the second time around. Any differences will be flagged and the user asked for corrections. The things that have to be carried out to accomplish the task are:
1. At the end of entry of the data for the first time, save a copy of the buffer to the history buffer, and enter "Verification mode"
2. As the user enters each field in the second pass, verify it against the History field.
3. If it doesn't match, have a popup screen that shows both values, and prompts the user for the correct value. Write the correct value into both the history field and the buffer (to prevent a mismatch being flagged as an error during the SELECT() on acceptance of the form.
The neatest way to provide the functionality is as a #CONTROL template. This will be attached to a Verification button that the user presses at the end of the first pass (Step 1 above). The attached code will carry out the other tasks. However, our first task is to write some new methods to add to the WindowManager class to extend the functionality of the HistoryField.
The WindowManager class contains a queue with one record for each control. In normal operation the first two fields of each queue record will be the same for each record. These are the addresses of the GROUP structures that are the record buffer itself, and the history buffer, that is declared as LIKE(Fil:Record). The whole queue structure is:
HistoryList QUEUE,TYPE
FRecord &GROUP !The file buffer address
SRecord &GROUP !The history buffer address
Control SIGNED !The number of the control on the screen
FieldNo SIGNED !The position of the field
!within the file (and history) buffer
END
The History methods do not deal with memos, and in fact there is a bug in the shipped classes that you will encounter if your file has memo fields that are declared first in the file structure. The workaround is to make sure that the memo field declarations occur at the end of the file definition. The queue is populated by the AddHistoryFile and AddHistoryField methods, which are placed in the WindowManager.Init. The records are added to the queue in order of the control number. I have added five new methods to the History field set. Based on the above, the comments should be comprehensible.
WindowManager.CheckSameness PROCEDURE(SIGNED control)
RVal BYTE(0)
CODE
!Make sure that the queue exists
IF ~SELF.History &= NULL
!Get the queue entry relating to this control
SELF.History.Control = Control
GET(SELF.History,SELF.History.Control)
IF ~ERRORCODE() !So long as the queue entry found
! If the contents of the matching field numbers
! in each of the buffers do not match
IF NOT WHAT(SELF.History.SRecord,|
SELF.History.FieldNo)|
= WHAT(SELF.History.FRecord,|
SELF.History.FieldNo)|
Rval = 1 !Return 1 showing a mismatch
END
RETURN Rval
END
END
WindowManager.GetPrevField PROCEDURE(SIGNED control)
RVal STRING(20)
CODE
IF ~SELF.History &= NULL
SELF.History.Control = Control
GET(SELF.History,SELF.History.Control)
IF ~ERRORCODE()
!Return the history buffer value for this field
Rval = WHAT(SELF.History.SRecord,|
SELF.History.FieldNo)
RETURN Rval
END
END
WindowManager.GetCurrField PROCEDURE(SIGNED control)
RVal STRING(20)
CODE
IF ~SELF.History &= NULL
SELF.History.Control = Control
GET(SELF.History,SELF.History.Control)
IF ~ERRORCODE()
!Return the file buffer value for this field
Rval = WHAT(SELF.History.FRecord,|
SELF.History.FieldNo)
RETURN Rval
END
END
WindowManager.FillCorrect PROCEDURE(SIGNED control)
Left ANY
CODE
IF ~SELF.History &= NULL
SELF.History.Control = Control
GET(SELF.History,SELF.History.Control)
IF ~ERRORCODE()
!Get the address of the field within the history buffer
Left &= WHAT(SELF.History.SRecord,|
SELF.History.FieldNo)
Left = WHAT(SELF.History.FRecord,|
SELF.History.FieldNo)
!Put the current value of the file buffer for this field
!in the matching place in the history buffer
PUT(SELF.History)
END
END
WindowManager.Fassign PROCEDURE(SIGNED Control,STRING svalue)
Left ANY
CODE
IF ~SELF.History &= NULL
SELF.History.Control = Control
GET(SELF.History,SELF.History.Control)
IF ~ERRORCODE()
!Get the address of the field within the file buffer
Left &= WHAT(SELF.History.FRecord,|
SELF.History.FieldNo)
!Fill it with the passed value
Left = svalue
DISPLAY(SELF.History.Control)
END
END
These methods will be called by a locally-declared WindowManager method that will carry out steps 2 and 3 above. We don't want to add this class to the main WindowManager methods because it:
This method is called CheckPrevious, and looks like this:
WindowManager.CheckPrevious PROCEDURE()
temp STRING(20)
CODE
!Only check if in verification mode
IF VerificationMode=1
! if the history and current mismatch
IF WindowManager.CheckSameness(FIELD())
! Pass the two old values and get the
! correct value on return
temp = EWindow.disperror(|
ThisWindow.GetPrevField(FIELD()),|
ThisWindow.GetCurrField(field()))
!If not blank
IF temp ~= ''
!Put the correct value into the file buffer
WindowManager.Fassign(FIELD(),temp)
!Copy the value into the history buffer
WindowManager.FillCorrect(FIELD())
END
END
END
Ewindow is a locally-declared instance of the WindowManager class. It opens a window showing the old and new values and has an entry field to accept the real value, which it passes back.
There are just a couple more little things to explain before we look at the control template itself. Firstly, you will probably want to store the verification level in each record of the file. I have used the scheme: 0 means unverified, 1 means verification is in progress, 2 means the record has been verified. Secondly, you may not want verification to be performed on all of the fields on the form. I have set up a scheme where only those fields where a token entered in the user options field in the dictionary, matches a token entered as a #PROMPT for the control template are verified. If the #PROMPT is left blank, then all fields on the form would be verified.
#CONTROL(DoubleEntryVerification,|
'Validate against
previous entry'),|
PROCEDURE
!This just adds the button to the form
CONTROLS
BUTTON('Verification'),USE(?VeriButton)
END
These are the two prompts that the programmer should fill in: the field to use to check whether the form is in "Verification mode", and the value in the User Options field in the dictionary to determine whether a particular field should be verified
#BOXED('Double Entry')
#DISPLAY('Conditions for validation..')
#PROMPT('Check&If:',@s12),%CheckifValue
#PROMPT('Validation variable:',FIELD),%ValidVar
#ENDBOXED
#AT(%WindowManagerMethodCodeSection,|
'TakeCompleted','(),BYTE'),PRIORITY(4000)
!On completion of the verifiction pass,
!this sets the validation variable to 2
IF %ValidVar=1
%ValidVar=2
END
#END
The #CALL and #ADD lines add the CheckPrevious method to the local class declaration. Working out how to do this was a total PITA that is belied by the seeming simplicity of the two lines.
The remainder of the section contains the window declaration of the error window and the values that will be displayed on it. Everything is declared as STRING(20). Numeric values are converted automatically. If you had to worry about strings longer than 20 characters, then the error window would have to be modified accordingly. It also contains the class declaration for the Ewindow instance of the WindowManager class. Only three derived methods are needed: Init, TakeAccepted and the method that is actually called by CheckPrevious: Disperror.
#AT(%DataSection),PRIORITY(2500)
#CALL(%SetClassItem,'Default')
#ADD(%ClassLines,|
'CheckPrevious PROCEDURE(),PROC')
OldValue STRING(20)
NewValue STRING(20)
RealValue STRING(20)
Errwindow WINDOW('Value correction'),AT(,,183,47),GRAY,MDI
STRING('Previous value'),AT(2,2),USE(?String1)
STRING(@s20),AT(55,2,82,9),USE(OldValue)
STRING('New value'),AT(2,15),USE(?String3)
STRING(@s20),AT(56,14),USE(NewValue)
STRING('Real value'),AT(2,32),USE(?String5)
ENTRY(@s20),AT(56,32,,10),USE(RealValue)
BUTTON('OK'),AT(146,2,35,14),USE(?EOkButton)|
,DEFAULT
END
EWindow CLASS(WindowManager)
Init PROCEDURE(),BYTE,PROC,VIRTUAL
TakeAccepted PROCEDURE(),BYTE,PROC,VIRTUAL
Disperror PROCEDURE(STRING OldValue,|
STRING NewValue),STRING,PROC,VIRTUAL
END
#ENDAT
This next section adds the code to the verification button. It sets "Verification mode" on the form and saves the current buffer to the history buffer, and then erases the entries in the file buffer (for the fields with matching user option field), and displays the blanked form.
#AT(%ControlEventHandling,'?Veributton','Accepted')
%ValidVar = 1
%WindowManager.SaveHistory()
#FOR(%Control),WHERE(%ControlType='ENTRY')
#FIND(%Field,%ControlUse)
#IF(INSTRING(%CheckIfValue,%FieldUserOptions))
ERASE(%Control)
#ENDIF
#ENDFOR
DISPLAY()
#ENDAT
The next code section will insert a call to the CheckPrevious method for all the fields that we want to check.
#AT(%ControlEventHandling,,'Accepted'),|
WHERE(%ControlType='ENTRY')
#FIND(%Field,%ControlUse)
#IF(INSTRING(%CheckIfValue,%FieldUserOptions))
%WindowManager.CheckPrevious
#ENDIF
#ENDAT
The final section contains four local class methods. One is the CheckPrevious method which belongs to the main WindowManager class for the form. The other three are the methods that control the error window.
#AT(%LocalProcedures,'Local Procedures')
%WindowManager.CheckPrevious PROCEDURE()
temp STRING(20)
CODE
IF %ValidVar=1
IF %WindowManager.CheckSameness(FIELD())
temp = EWindow.disperror(|
ThisWIndow.GetPrevField(FIELD()),|
ThisWindow.GetCurrField(FIELD()))
IF temp ~= ''
%WindowManager.Fassign(FIELD(),temp)
%WindowManager.FillCorrect(FIELD())
END
END
END
EWindow.disperror PROCEDURE(STRING PassOldValue,|
STRING PassNewValue)
CODE
!Don't keep values from previous error correction
CLEAR(realvalue)
OldValue = PassOldValue
NewValue = PassNewValue
!Calls the init() method, then ask()
GlobalResponse = EWindow.Run()
! This property is set by the Kill() method on exit.
! If true, the window will not open the next time
! we call it, so we set it to false.
SELF.Dead = False
! The WindowManager class surprisingly never
! explicitly closes its windows, but leaves |
! this to happen implicitly on a procedure |
! return. Here we need to explicitly close |
! our error window to return control to the |
! main window.
CLOSE(ErrWindow)
RETURN(realvalue) !Return the value entered in the entry field.
EWindow.Init PROCEDURE()
ReturnValue BYTE,AUTO
CODE
SELF.Request = SelectRecord
ReturnValue = PARENT.Init()
IF ReturnValue THEN RETURN ReturnValue.
SELF.FirstField = ?EOkButton
SELF.Errors &= GlobalErrors
OPEN(Errwindow)
SELF.Opened=True
RETURN ReturnValue
EWindow.TakeAccepted PROCEDURE()
ReturnValue BYTE,AUTO
Looped BYTE
CODE
LOOP
IF Looped
RETURN Level:Notify
ELSE
Looped = 1
END
CASE ACCEPTED()
OF ?EOkButton
EWindow.PostCompleted
POST(Event:Closewindow)
RETURN LEVEL:Fatal
ELSE
RETURN Level:Benign
END
END
#ENDAT
That's all. Using it is as simple as adding the control to any form where the history key is enabled, specifying the variable to use to indicate verification mode, and marking fields as requiring double entry in the dictionary (if desired).
There are three files that accompany this article. One is the set of five new methods to add to the ABWindow.CLW file, one is the set of method prototypes to put into the ABWindow.inc file, and the third is the #CONTROL template, which can be added to a file in the ABC chain, for example, at the bottom of the ABUpdate.TPW file.
Copyright © 1999-2008 by CoveComm Inc. All Rights Reserved. Reproduction in any form without the express written consent of CoveComm Inc., except as described in the subscription agreement, is prohibited.
Clarion Magazine ISSN 1718-9942
One year: $184
(includes all back issues since '99)
Renewals from $134
Two years: $274
Renewals from $224