Excel VBA: Validating User Input Part 2. Excel VBA 2 Lesson 20

 

HOME | Back to index  <Previous | Next> Buy the complete Excel course, Including VBA & Workbook examples for only $9.95 | Ask a question?

Checking for Valid Choices

The advantages common to all the above methods is that the user does not need to type, which is faster and means no typos.

When to Apply Validation

A decision for the developer is whether to check a single value when exiting a control, or to check all the values when the user clicks a button to single completion.  A hybrid method often works well, checking individual controls as they are completed, and then checking when the UserForm is complete that all mandatory controls are taken care of.

Sources of Invalid Data

Invalid data is encountered one of two ways.  It can be provided by the workbook or by the user.  If it is provided by the workbook, then it can be static (available before UserForm is started) or dynamic (available after UserForm is started).

Bad static data, usually in the form of a bad entry in a list, is the responsibility of the person maintaining the workbook.  Bad static data could also result from a SpinButton with the wrong limits set.

Bad dynamic data, also usually in the form of a bad entry in a list, is the responsibility of the programmer.  It often results from data validation code failing to detect invalid data provided by the user.

When a user’s input is checked and added to a list, it is still possible that it is not valid for the list.  This is now a user problem.  If additional checks can be identified by the user, then the developer can enhance the validation accordingly.  Sometimes allowable data is so variable that it is virtually impossible to account for all possible invalid combinations. 

Validating data requires an understanding of how it is to be used.  If the data is to be a date or a number, VBA provides the IsDate and IsNumeric functions.  Even though a number is needed, it is not always valid.  For example, zip codes are a special case, best treated as text because zip codes can have leading zeroes, and numbers typically don’t.  Zip codes are known length, makes sense to check when length is 5 and again when length is 9.  not all zip codes exist, and it’s best to check against

If Not IsDate(var) Then
    MsgBox "Your entry must be part of the list", vbCritical
    Exit Sub
End If

If Not IsDate(var) Then
    MsgBox "Your entry must be part of the list", vbCritical
    Exit Sub
End If

Where free-form entry is necessary, the developer must decide whether to validate after every keystroke or only when the entry is complete.

Code Cautions for Testing Data

Checking data is not necessarily as easy as it seems.  The method of checking itself may encounter an error, and these errors must be correctly by the developer so that the only errors that occur are the detecting of invalid data.  For example, in UserForm_Lesson03a, the OptionButtonSeeW is a TripleState control and is initialized to Null.  The following statement will fail even though the value is not true.

If OptionButtonSeeW.value = False then

In such a case the value must be specifically checked for Null as well as False and True

Const var As Integer = 1

If var = "abc" Then MsgBox "xx"

The above code will cause a Type Mismatch error because var is not a string.  Likewise, the following code will cause a Type Mismatch error because var is not an integer.

Const var As String = "abc"

If var = 1 Then MsgBox "xx"

For this reason, variables to receive TextBox.Value and ComboBox.Value must be of type Variant, which can take any type of data.  In the following code, the test of var will evaluate to False, but the code itself will not fail.

Dim var As Variant

var = 1

If var = "abc" Then MsgBox "xx"

var = "abc"

If var = 1 Then MsgBox "xx"

Examples of Verification by Code

When validating entries, be as pessimistic as possible.  If a user can get it wrong, they will!  Often it will not be possible to account for all possible errors.  Sometimes the developer can only minimize the damage.  In the end, responsibility falls on the user.

If the user has been allowed to type an entry, either in TextBox or in the TextBox part of ComboBox, then the entry must be checked for where it will be used.  For example, it can be validated as a number, but furthermore, if it is to be a US ZIP code, then it can be checked to see in string form it is 5, 9, or 10 digits long.  If 10 digits, then it should be of the form zzzzz-nnnn.

A string entry may be quite complex, with multiple parts, where parts may be dates, numbers, etc.  Rather than devise a complex way to parse such a string, it is better to change the design so the various parts are supplied by different controls.

Many different kinds of data can at least have some checking done, as can be seen from the following examples.

Check if Number

If Not IsNumeric(TextBox1.Value) Then
    MsgBox "Numbers only", vbCritical
    Exit Sub
End If

Check if Text

If IsNumeric(TextBox1.Value) Then
    MsgBox "Text only", vbCritical
    Exit Sub
End If

Check if Date

Dim s$

s$ = """" & TextBox1.Value & """"

If IsDate(s$) Then

    MsgBox " Date only", vbCritical
    Exit Sub
End If

IsDate requires a text string.  TextBox returns a Variant value, so it must be surrounded with two pair of double quotes.  The code strips one pair of double quotes, so one pair remains when it is submitted to IsDate.

Lesson 10 will present the DTPicker control for selecting valid dates.

Check if Time

Dim dTime As Date
If TextBox1.Text <> "12:00:00 AM" Then
    On Error Resume Next
    dTime = WorksheetFunction.Sum(TextBox1.Value, 1)
    On Error GoTo 0
    If dTime > DateValue("1/1/1900") Or dTime = "12:00:00 AM" Then

        MsgBox "Times only", vbCritical
        Exit Sub
    End If
End If

This is a rather obscure check.  The default value of a Date variable is 12:00:00 AM.  It is a number, so if text is added to it the result will still be 12:00:00 AM.  A valid time is a number less than 1 (see http://excel.tips.net/Pages/T002176_How_Excel_Stores_Dates_and_Times.html), so if 1 is added to it and submitted to the DateValue function, it will return a result that is less than the date 1/1/1900.

The On Error statement is needed for the code to keep operating if an error occurs.  It will be discussed more in the Handling Invalid Data section below.

Check if Cell Address

Dim rCheck As Range

 

On Error Resume Next
Set rCheck = Range(TextBox1.Value)
On Error GoTo 0

If rCheck Is Nothing Then
    MsgBox "Cell address only", vbCritical
    Exit Sub
End If
Set rCheck = Nothing

            Lesson 10 will present the RefEdit control for selecting and validating addresses.

Check if Email

If InStr(1, TextBox1.Text, "@", vbTextCompare) = 0 Then
    MsgBox "Not a valid email", vbCritical
    Exit Sub
End If

The same principle can be used to check for a Web address (www or http or .com etc) see Excel VBE help on InStr Function!

In UserForm Control Demos.xls, click “Validate Data” on sheet Lesson 9 for implementation of the above six checks

Check if Exists Already

This checks if a value is already present in a range of cells.  If the cells are the RowSource of a ComboBox or ListBox, then it checks whether the value is included in the control List.

If WorksheetFunction.CountIf(Sheet1.Range("A1:C200"), TextBox1.Text) <> 0 Then
    MsgBox "Entry is already part of the Table", vbCritical
End If

The same principle can be used to check whether an entry doesn't exist already simply by changing the comparison operator to =

Check if File or Folder Exists

This check will not be needed if the folder or file name was obtained from the GetOpenFilename dialog box as described above.  If a path is from a cell on a worksheet, then the check is useful to ensure the path is still valid.

With Application.FileSearch
    .NewSearch
    .LookIn = "C:/MyDocuments"
    .SearchSubFolders = True
    .Filename = TextBox1.Text
    .MatchTextExactly = True
    .FileType = msoFileTypeAllFiles
    If .Execute = 0 Then

        MsgBox "File does not exist", vbCritical

        Exit Sub

    End If

End With

This check does not return any information about where the file or folder name was found, only that it exists.  The FileSearch Property has many other possibilities.  Read the Excel VBE help on the FileSearch Property for more information.

Handling Invalid Data

There three concerns when handling invalid data.

  1. The code must keep going when invalid data causes an error.
  2. The error must be posted with MsgBox for the user to see, and optionally logged to a file.
  3. The user should be directed to the source of the error so correction can be made.

On Error

The On Error statement is provided for code to survive run-time errors.  It comes in two forms, GoTo and Resume Next.  On Error GoTo 0 is a special form which clears the error and resets the error handling.

The Resume Next form most often used just to detect errors

On Error Resume Next                ' start error handling

isAddress = IsObject(Range(s$))

On Error GoTo 0                     ' clear error handling

The GoTo form can be used when complex handling of the error is required.  The first example has an inline error handler where code execution resumes after the handler.  The second example has the error handler at the end of the procedure.  It can be allowed to exit the procedure after handling the error.  The third example is the second ending with a Resume Next statement, which has the same effect as Example 1 with inline error handling

EXAMPLE 1

    On Error Resume Next            ' start error handling

    isAddress = IsObject(Range(s$))

    If Err.Number = 0 Then GoTo noError

    '< code if an error occurred>

noError:

    '< code if no error occurred>

    On Error GoTo 0                 ' clear error handling

EXAMPLE 2

    On Error GoTo errorHandler      ' start error handling

    isAddress = IsObject(Range(s$))

    '< code if no error occurred>

    Exit Sub

errorHandler:

    '< code if an error occurred>

    On Error GoTo 0                 ' clear error handling

    ' focus is set to the failing control and code is allowed to exit sub

EXAMPLE 3

    On Error GoTo errorHandler      ' start error handling

    isAddress = IsObject(Range(s$))

    '< code if no error occurred>

    Exit Sub

errorHandler:

    '< code if an error occurred>

    On Error GoTo 0                 ' clear error handling

    Resume Next                     ' return to normal execution

As far as choosing one form over another, Example 1 is good for short procedure, where the code flow is easily seen.  The other two examples are more useful where there is a lot of code in the non-error flow.  It moves the error handling to the bottom of the procedure where it doesn’t interfere with understanding the main code Flow.  Example 2 is most often used with UserForms, because the focus can be set back to the failing control before the procedure ends.

MsgBox

MsgBox is the usual method of choice to notify the user of an error to be corrected.  It has the power to insist the error be seen by the user.  While its normal use is to post one error, a message can be accumulated in a checking loop and display just once after the loop with all the errors.

MsgBox can display one of four different icons, which can be used to indicate the severity of the error.  The four icons are Critical, Question, Exclamation, and Information.  In general, fatal errors will be posted with a Critical MsgBox,

MsgBox also has built in buttons for the user to select an action.  There are six button choices

Display OK button only.

Display OK and Cancel buttons.

Display Abort, Retry, and Ignore buttons.

Display Yes, No, and Cancel buttons.

Display Yes and No buttons.

Display Retry and Cancel buttons.

A hierarchy can be defined for a project, where Critical errors must be fixed, Question errors may be ignored, and Exclamation errors may be retried, and Information conditions are only logged to the log file.  Note that if decision buttons are included, then MsgBox must be used as a function, with an integer return value:

MsgBox "This is a Critical message box", vbCritical

returnValue% = MsgBox("This is a Question message box", _

  vbQuestion + vbAbortRetryIgnore)

returnValue% = MsgBox("This is an Exclamation message box", _

  vbExclamation + vbRetryCancel)

MsgBox "This is an Information message box", vbInformation

    

The message presented by the MsgBox should state what the problem is, and either suggest corrective action, or put the user back on the UserForm at the point of the error so it may be corrected.  “Invalid Data” messages are useless.  Cancel and Ignore buttons should lead to Exit Sub statements which ends the validation attempt.  If multiple errors are being processed, then they should go on to deal with the next error in the list.

Log File

Some projects will have a need for the invalid data history.  In those cases, the information can be written to a log file.  By logging after the MsgBox, the log record can include what button on the MsgBox was clicked.

retValAry = Array("OK", "Cancel", "Abort", "Retry", "Ignore", "Yes", "No")

Print #1, "Invalid Data", control.Name, value, retValAry(returnVal%)

Writing to a log file was covered in Lesson 7.

Set Focus

If a value is invalid, the SetFocus method is used to put the cursor into the failing control so the user can correct the value.  The Tag property is useful because it can be set at design time and save with the file.

EXERCISE

1.    Insert a UserForm into VBAProject. 

2.    On it create six TextBox controls. 

3.    Format them with Centers aligned. 

4.    From top to bottom change their

a.     Name properties to: HomeEmail, WorkEmail, Age, PayNumber, Telephone, and StAddress

b.    Value properties to: *YourEmail@Home, *YourEmail@Work, *Your Age, *Your Pay Number, Telephone, and Street Address

c.     Tag properties to “Check”

5.    At the top of the TextBoxes place a Label control and change it's Caption Property to: Please note * denotes required entry

6.    Finally add a CommandButton, rename to “OKButton” and change Caption “OK”

Code the OKButton_Click event to require the user to fill in all required entries.

' module level variables

Dim ctrlControl As Control
Dim bCancel As Boolean

 

Private Sub OKButton_Click()
    For Each ctrlControl In Me.Controls
        If ctrlControl.Tag = "Check" Then
            ctrlControl.SetFocus
            ValidationCheck            ' Private Sub to check entries

        End If
        If bCancel = True Then Exit For

    Next ctrlControl
    If bCancel = True Then Exit Sub

    '<Some carry on code here>
End Sub

OKButton_Click will loop through all controls on the UserForm and perform the validation check if so indicated by the Tag property.  Each validation procedure will set bCancel to True if there is an error.  If a validation error occurs, the validation procedure will return with bCancel set to True, and the OKButton_Click code will exit with focus set to the failing control.

Private Sub ValidationCheck()
    Dim ValToCheck, msg$ As String

    ValToCheck = Me.ActiveControl.Value

    Select Case ActiveControl.Name
        Case "HomeEmail", "WorkEmail"
            If Left(ValToCheck, 11) = "*YourEmail@" Then
                msg$ = "Please enter an email"

            ElseIf InStr(1, ValToCheck, "@", vbTextCompare) = 0 Then
                msg$ = "Please enter a valid email"
            End If
        Case "Age
            Select Case ValToCheck
                Case vbNullString, "*Your Age", Not IsNumeric(ValToCheck)
            msg$ = "Please check your age"
        Case ", "PayNumber""PayNumber"
            Select Case ValToCheck
               Case vbNullString, "*Your Pay Number", Not IsNumeric(ValToCheck)
            msg$ = "Please check your pay number"

    End Select

    If msg$ = "" Then
       bCancel = False
    Else
       bCancel = True

       MsgBox msg$, vbCritical

       ' optional log file step would go here
    End If
End Sub

NOTES:

1.    Procedure is a Private Sub as it is residing in the Private module of the UserForm.

2.    The variable "ValToCheck" is declared as a Variant (default) as it may hold either text or numbers.  "msg$" will be used in a message box.

3.    Because the calling procedure set focus to this control, a Select Case Statement can used on the ActiveControl name.

4.    The control names set at design time are used to determine which validation to perform.

5.    Each validation must allow for the default value and for a “” value (NullString) caused by the user hitting the Delete key.

6.    "HomeEmail" and "WorkEmail" uses a simple If statement to check whether the entered value an  "@".

7.    “Age” and “PayNumber” are checked with another Select Case Statement to find out 1) if it's blank, 2) if it still has its default value and 3) if it contains text.

8.    If any of the above are True, the variable is set to relevant text.

9.    After the original Select Case statement, if the string variable is not empty, the user has not correctly filled in the UserForm, so the string variable is displayed.

10.  After the user clicks OK on MsgBox, the code returns to the loop.

The advantage of setting focus to the failing control is that the cursor will be flashing, leading the user to the error. 

Accumulating Validation Errors

In the special case where errors are to be accumulated in a loop, the message string accumulates with linefeed characters between the messages. 

msg$ = msg$ & IIf(msg$ = "", "", " & Chr(10) & ") & "this message"

The MsgBox will display all the errors at once.  It will be closed before the user has control and can make the corrections.  The user will have to find and correct the problems without benefit of the cursor blinking control with the problem.  This situation is one where having a log file listing the errors will be very useful.

HOME | Back to index  <Previous | Next> Buy the complete Excel course, Including VBA & Workbook examples for only $9.95 | Ask a question?

 

Microsoft ® and Microsoft Excel ® are registered trademarks of Microsoft Corporation. OzGrid.com is in no way associated with Microsoft.

OzGrid.com accepts no responsibility for any adverse affect that may result from undertaking our training.