Part of the problem with padding a key field is that, in some cases, it could be alphanumeric. For example, a key field might be 12 characters long but also be required to allow the entry of values such as PROJECT00001 or 000400. In this article, Doron describes a class that resolves this problem. I was faced with a problem when my client requested that I allow the user to enter alphanumeric characters in the key field. It got even better when I had to pad the string only if the user entered a string consisting only of numeric characters.
If the user entered an alphanumeric combination, there was no need to pad the key field. Of course, it's required to check whether it's a unique number, but that's beside the point. At the beginning, the function determines the length of the field and the number of characters that were entered. It also checks whether an object or a MemVar was passed.
Since I might not need to pad the object directly in some cases, a text box is an object in this case. At this point, I was wondering if there's a native function in VFP that could tell me whether there's a character within a particular string. ISALPHA() can find only the left- most alpha character, but what happens if the key field is something like 55UA78? In this case, ISALPHA() wouldn't find the alpha character.
I thought that there might have been a hidden function that's yet to be discovered, but no such luck. I went to CompuServe, thinking that maybe somebody would direct me to such a function. One suggestion was the expression shown in Listing 1.
IF LEN(CHRTRAN(UPPER(m.lcNewValue) , ;
'"'+"ABCDEFGHIJKLMNOPQRSTUVWXYZ~!@#$%^&*()_+`-=|}{[]|;:?/>.<,
' ","")) <> LEN(m.lcNewValue)
m.llIsAlpah=.T.
ENDIF
I didn't think that the preceding solution was the best one-I suspected it would be faster to do it with ISALPHA(SUBSTR(m.lcNewValue,m.nJ,1)) and enclose that expression within a FOR..ENDFOR loop.
I decided at first to use the following LOOP, with ISALPHA(SUBSTR(m.lcNewValue,m.nJ,1)), and enclose this expression within a FOR..ENDFOR loop (see Listing 2). The number of loops depends on the length of the field. Once an alpha character is found, the loop is terminated.
To determine which expression is faster, I tested both within a field that contained the value "123456789012345s".
SET FIXED ON
SET DECIMALS TO 18
m.Start=SECONDS()
FOR m.nJ=1 TO m.nlNumOfChr
IF ISALPHA(SUBSTR(m.lcNewValue,m.nJ,1))
m.llIsAlpha=.T.
EXIT
ENDIF
ENDFOR
m.End=SECONDS()
m.Total= m.End- m.Start
Repeated tests of the code shown in Listing 2 took either 0.0009999999999 (1 /10000 of a second) or 0.0000000000. The code in Listing 1 showed all times as 0.00000000000000000 seconds. Since the field had 16 characters and only the last one was an alpha, 16 loops were executed to determine whether there was a character within the string. With a string of 30 characters, the results were the same amount of time.
The only problem I found with Listing 1 is that the string is hard-coded foreign characters aren't included. I decided to use the FOR..ENDFOR expression to be on the safe side.
The flag m.llIsAlpha is assigned a .T. value when there's an alpha character within the m.lcNewValue string, and thus there's no need to pad that string. If no alpha character is found, then I need to check whether the number of characters entered was equal to the field's length.
At the beginning of that class, I determine whether this is an object or just a MemVar that's to be processed, and set m.llIsObject to indicate so. If it's .T., then it's an object; if .F., it's a MemVar.
IF TYPE('oKeyFld')=="O"
m.nlFldLen=LEN(oKeyFld.Value) m.lcNewValue=ALLTRIM(oKeyFld.Value)
m.llIsObject=.T.
ELSE
m.nlFldLen=LEN(oKeyFld) m.lcNewValue=ALLTRIM(oKeyFld)
m.llIsObject=.F.
ENDIF
In addition the class determines if the m.lcNewValue value was zero or a negative number The following is the code:
IF VAL(m.lcNewValue)<=0
IF m.llIsObject
oKeyFld.Value=This.DisplayMessage(m.nlFldLen)
This.llIsEmpty=.T.
ELSE
This.lcRetValue=This.DisplayMessage(m.nlFldLen)
ENDIF
ELSE
IF ! m.llIsObject
This.lcRetValue=m.lcNewValue
ENDIF
ENDIF
RETURN
Init Method
********************************************************************************
*** Procedure: Init
*** Author: Doron Farber
*** Tel: 732-536-4765
*** Created: Feb 12, 1998
*** Copyright: (c) The Farber Consulting Group, Inc.
*** Purpose: This class can directly pad the current text box, and
check if numeric
*** characters are less then the field length. If the value is zero
it pops up
*** a message and return a space in the field's length. The
developer may
*** check IF ! EMPTY(). It also checks if one alpha character
included in
*** the text box, if found do not pad the field, leave it as is. In
most cases
*** it should be called from the TextBox valid method.
*** Parameters: oKeyFld - An object reference to current active
text box, or MemVar value.
*** MemVars: m.lcIsAlpah - A flag whether an alpha character is
included in a MemVar or This.Value
*** m.nlNumOfChr - Tell us how many characters were entered into
the field.
*** m.nlFldLen - Tell us the field length.
*** m.J - A Counter for the FOR .. ENDFOR loop.
*** m.lcNewValue - Current trimmed value entered into the
field.
*** Properties: lcRetVlaue - Gets its value when a memvar is
passed, this memver
*** could be the text box value.
*** llIsEmpty - Gives an indication if the textbox is empty.
*** Calling: a) oPadKeyFld=CREATEOBJECT("PadKeyFld",This) Modify
the text box.
*** b) oPadKeyFld=CREATEOBJECT("PadKeyFld",m.NowValue)
*** Returns value, When m.NowValue could be textbox value or any
MemVar.
*** Return: oPadKeyFld.lcRetValue property, is used only for a
NemVar as a
*** return value, m.lcNewValue=oPadKeyFld.lcRetValue
*** oPadKeyFld.llIsEmpty - When the textbox is empty it returns
.T.
*** Notes: Use calling item (b) only if you do not wish to modify
the text box directly.
*******************************************************************************
LPARAMETERS oKeyFld
LOCAL
m.llIsAlpah,m.nlNumOfChr,m.nlFldLen,m.nJ,m.lcNewValue,m.llIsObject
m.llIsAlpah=.F.
This.llIsEmpty=.F.
IF TYPE('oKeyFld')=="O"
m.nlFldLen=LEN(oKeyFld.Value)
m.lcNewValue=ALLTRIM(oKeyFld.Value)
m.llIsObject=.T.
ELSE
m.nlFldLen=LEN(oKeyFld)
m.lcNewValue=ALLTRIM(oKeyFld)
m.llIsObject=.F.
ENDIF
m.nlNumOfChr=LEN(m.lcNewValue)
*** Is any Alpha character included ??
FOR m.nJ=1 TO m.nlNumOfChr
IF ISALPHA(SUBSTR(m.lcNewValue,m.nJ,m.nJ))
m.llIsAlpah=.T.
EXIT
ENDIF
ENDFOR
*** Are number of characters smaller then text box length
IF m.nlNumOfChr
*** Is it numeric
IF ! m.llIsAlpah
*** Is it a memvar or an object
IF m.llIsObject
m.lcNewValue=PADL(m.lcNewValue,m.nlFldLen,"0")
*** Make sure key value is greater then zero, if Zero return blank
field with spaces
IF VAL(m.lcNewValue)>0
*** Change the value of the object
oKeyFld.Value=m.lcNewValue
RETURN
ENDIF
ELSE && IF m.llIsObject
*** So, it is not an object it is a MemVer
m.lcNewValue=PADL(m.lcNewValue,m.nlFldLen,"0")
IF VAL(m.lcNewValue)>0
This.lcRetValue=m.lcNewValue
RETURN
ENDIF && IF VAL(m.lcNewValue)>0
ENDIF && IF m.llIsObject
ELSE
*** It has an alpha character nothing to do
This.lcRetValue=m.lcNewValue
RETURN
ENDIF && IF ! m.lcIsAlpah
ELSE && IF m.nlNumOfChr
** Does it have the maximum required characters? But make sure it
has at
** least one alpha character, so it will not be considered as zero.
Since
** VAL(A22) or VAL(Z44) will produce a Zero, no matter which
alpha
** character will be in front
IF m.llIsAlpah
IF ! m.llIsObject
This.lcRetValue=m.lcNewValue
ENDIF
*** If it is an object do nothing on the text box
RETURN
ENDIF
ENDIF && IF m.nlNumOfChr
*** Make sure key value is greater then zero, if Zero
VAL(m.lcNewValue)<=0 return
*** blank field with spaces. This is occurs when either
m.nlNumOfChr==m.nlFldLen
*** or m.nlNumOfChr
IF VAL(m.lcNewValue)<=0
IF m.llIsObject
oKeyFld.Value=This.DisplayMessage(m.nlFldLen)
This.llIsEmpty=.T.
ELSE
This.lcRetValue=This.DisplayMessage(m.nlFldLen)
ENDIF
ELSE
IF ! m.llIsObject
This.lcRetValue=m.lcNewValue
ENDIF
ENDIF
RETURN
The Display Message method is called from the init(0 method and provides an error message when the value of the key field is zero or negative number.
*******************************************************************************
*** Program: DisplayMessage
*** Copy Right: The Farber Consulting Group, Inc.
*** Author: The Farber Consulting Group Inc. By Doron Farber
*** Purpose: To display an error message when no value entered, or
not
*** greater then zero.
*** Parameters: m.nlFldLen - Gets the length of either, the text
box or memver
*** MemVars: m.lcMessageTitle - Holds the message title
*** m.lcMessageText - Holds the actual message
*** Calling: It is called from the init method of this class.
*** Return: SPACE(m.nlFldLen)
*** Notes: None
*******************************************************************************
LPARAMETER m.tnlFldLen
LOCAL m.lcMessageTitle,m.lcMessageText
m.lcMessageTitle = 'Key Field Is Zero!!
m.lcMessageText = 'You Must Enter A Value Greater Then Zero'
*** ** MB_OK+MB_ICONSTOP ******
=MESSAGEBOX(m.lcMessageText, 0 + 16, m.lcMessageTitle)
RETURN SPACE(m.tnlFldLen)
When the key value is zero or negative, DisplayMessage returns a string of spaces equal to the length of the field. This class can be called in one of two ways. Both are fired from the text box valid method.
If the text box is empty, the class will display a message to alert the user. Therefore, just RETURN 0 from the valid method. llIsEmpty will be .T. the following is sample code to be placed within the text box valid method:
LOCAL oPadKeyFld
oPadKeyFld=CREATEOBJECT("PadKeyFld",This)
IF oPadKeyFld.llIsEmpty
RETURN 0
ELSE
RETURN
ENDIF
You can also call it from the valid method but have changes performed indirectly on the text box. The following is sample code to be placed within the text box valid method:
LOCAL m.lcNewValue, oPadKeyFld
m.lcNewValue=This.Value
oPadKeyFld=CREATEOBJECT("PadKeyFld",m.lcNewValue)
IF EMPTY(oPadKeyFld.lcRetValue)
RETURN 0
ELSE
This.Value=oPadKeyFld.lcRetValue
RETURN
ENDIF
In the main program of your application, enter the following line of code:
SET CLASSLIB TO FcgUtils ADDITIVE
Please look into the sample form named PadKey. I have two samples of code within the valid method of each field. The full source code of the preceding function is included with the Subscriber Downloads at http://www.pinpub.com/foxtalk. In its simplest form, you'd just plug oPadKeyFld=CREATEOBJECT("PadKeyFld",This) within the valid method of the text box object, and it will just kick in.