|  | Click Here!

Search the site:
EXPERT SEARCH

| Programming Languages
| Databases
| Security
| Web Services
| Network Services
| Middleware
| Components
| Operating Systems
| User Interfaces
| Groupware & Collaboration
| Content Management
| Productivity Applications
| Hardware
| Fun & Games

EarthWeb sites
Crossnodes
Datamation
Developer.com
DICE
EarthWeb.com
EarthWeb Direct
ERP Hub
Gamelan
GoCertify.com
HTMLGoodies
Intranet Journal
IT Knowledge
IT Library
JavaGoodies
JARS
JavaScripts.com
open source IT
RoadCoders
Y2K Info

#

- Day 5
  - Input Validation
    - What Is Input Validation?
    - Common Input Validation Rules
    - Field-Level Validation
      - Filtering Keyboard Input
        - Figure 5.1.
    - Listing 5.1. Limiting data entry in the Keypress event.
    - Listing 5.2. The KeyPress event with control characters.
      - Figure 5.2.
    - Listing 5.3. The KeyPress event to force letters to uppercase.
      - Figure 5.3.
    - Listing 5.4. A single KeyPress event to check for valid entry and force uppercase.
      - Input Masking
        - Figure 5.4.
        - Figure 5.5.
        - Figure 5.6.
      - Validation Lists
        - Figure 5.7.
    - Listing 5.5. The Form_Load event to load a list box.
    - Form-Level Validation
      - Independent Content ValidationHigh/Low Ranges
        - Figure 5.8.
    - Listing 5.6. The form-level validation routine to check for values in a range.
      - Independent Content ValidationMin/Max Field Lengths
    - Listing 5.7. The form-level validation routine to check the length of fields and a valid range of values.
      - Required Fields
    - Listing 5.8. The form-level validation routine to check for required fields.
      - Conditional Fields
    - Listing 5.9. The form-level conditional validation routine.
    - Creating Your Own Validation Custom Control
      - Creating a Custom Control
        - Figure 5.9.
      - Laying Out the Control and Building the Maintenance Routines
        - Figure 5.10.
    - Listing 5.10. Adding code to the UserControl_Resize event.
    - Listing 5.11. Adding code to the UserControl_InitProperties event
    - .
      - Coding the KeyFilter Validations for the Custom Control
    - Listing 5.12. Adding the KeyFilter declarations to the VText control.
    - Listing 5.13. Adding KeyFilter defaults to the InitProperties event.
      - Figure 5.11.
    - Listing 5.14. Coding the Let and Get routines for the UpperCase property.
    - Listing 5.15. Coding the Digits property Let and Get routines.
    - Listing 5.16. Coding the Numeric property Let and Get routines.
    - Listing 5.17. Coding the Text property Let and Get routines.
      - Figure 5.12.
    - Listing 5.18. Coding the WriteProperties event of the VText control.
    - Listing 5.19. Coding the ReadProperties event for the VText control.
    - Listing 5.20. Coding the KeyValidate method.
    - Listing 5.21. Coding the Text1_KeyPress event.
      - Testing the KeyFilter Options of the VText Control
        - Figure 5.13.
        - Figure 5.14.
        - Figure 5.15.
    - Listing 5.22. Coding the error events for VText.
      - Adding Range-Checking Options to the VText Control
    - Listing 5.23. Declaring range-checking properties, events, and methods.
    - Listing 5.24. Coding the Let and Get property routines.
    - Listing 5.25. Adding the range-checking properties to the InitProperties event.
    - Listing 5.26. Modifying the ReadProperties event.
    - Listing 5.27. Modifying the WriteProperties event.
    - Listing 5.28. Coding the InRangeValidate method.
      - Testing the InRange Options of the VText Control
        - Figure 5.16.
    - Listing 5.29. Adding code to handle the InRange properties.
      - Adding CheckSize Options to the VText Control
    - Listing 5.30. Modifying the declaration section of VText.
    - Listing 5.31. Modifying the InitProperties event.
    - Listing 5.32. Modifying the ReadProperties event.
    - Listing 5.33. Modifying the WriteProperties event.
    - Listing 5.34. Coding the Let and Get methods for the CheckSize properties.
    - Listing 5.35. Coding the CheckSizeValidate method.
    - Listing 5.36. Modifying the Lost_Focus event of the TextBox.
      - Testing the CheckSize Options of the VText Control
        - Figure 5.17.
    - Listing 5.37. Coding the error message for vtxCheckSize.
    - Listing 5.38. Modifying the cmdOK_Click event code.
      - Adding Required and Conditional Options to the VText Control
    - Listing 5.39. Modifying general declarations for the Required option.
    - Listing 5.40. The final UserControl_InitProperties event code.
    - Listing 5.41. Coding the Let and Get methods for the new properties.
    - Listing 5.42. Complete code for the WriteProperties event of the UserControl.
    - Listing 5.43. Final version of the ReadProperties event.
    - Listing 5.44. Coding the CheckRequired method.
    - Listing 5.45. Coding the ConditionalValidate method.
    - Listing 5.46. Updated Text1_LostFocus event.
      - Testing the Required and Conditional Options of the VText Control
        - Figure 5.18.
    - Listing 5.47. Adding code for the Required and Conditional options.
      - Compiling and Registering the VText Control
    - Building the CompanyMaster Input Form
      - Defining the Basic Form
        - Figure 5.19.
      - Placement of Input Controls and Prompts
        - Figure 5.20.
        - Figure 5.21.
      - Adding and Coding Command Buttons
        - Figure 5.22.
    - Listing 5.48. Adding data records.
    - Listing 5.49. Unloading the CompanyMaster form.
    - Listing 5.50. Finding data records.
    - Listing 5.51. Restoring the data controls.
    - Listing 5.52. Writing a record.
    - Listing 5.53. Deleting a record.
    - Listing 5.54. Updating the company logo.
      - Adding Input Validation
    - Listing 5.55. Coding the error messages for the required fields.
    - Listing 5.56. Performing validation checks.
    - Listing 5.57. Calling validation routines when the Update button is pressed.
    - Summary
    - Quiz
    - Exercises

## Day 5

## Input Validation

Today you learn about one of the most important aspects of database programming--input validation. Validating user input before it is written to the database can improve the quality of the data stored in your tables. Good validation schemes can also make your program user friendly and, in many cases, can increase the speed at which users can enter valid data.

We cover several specific topics on input validation, including the following:

- Field-level validation versus form-level validation

- How to speed data entry by filtering keyboard input

- How to use input masks to give users hints when entering data

- How to limit user choices and speed input with validation lists

- How to handle required field inputs in Windows forms

- How to handle conditional field input validation in Windows forms

After you learn how to develop input validation routines, you build a set of validation custom controls. These custom controls handle seven valuable validation routines that you can use in your projects throughout the book. You can also use these custom controls in any project you build in the future.

Also, today is the day you build the main data entry form for the MASTER.MDB database. You use this database and data entry form in several chapters of the book. You use all the techniques you've learned this week, including the use of bound data controls and input validation to build a solid data entry form.

Before you get into the details of how to perform input validation, let's first talk about what input validation is and why it is so important to good database application design.

### What Is Input Validation?

Input validation is the process of checking the data entered by the user before that data is saved to the database. Input validation is a proactive process--it happens while data is being entered. Input validation is not the same thing as error trapping. Error trapping is a reactive process--it happens after the data has been entered. This is an important point. Input validation should be used to prevent errors. If you have good input validation schemes, you have fewer errors to trap! You learn more about the reactive process on Day 14, "Error Handling in Visual Basic 5."

Input validation can be used to give users guides on how to enter valid data. The best example of this kind of input validation is the use of a validation list. A validation list is a list of valid inputs for a field. If the user has only a limited number of possible valid choices for an input field, there is much less chance of a data entry error occurring. Good validation schemes give the user a list of valid input from which to choose while performing data entry.

Input validation can automatically edit data as the user enters it, instead of telling the user to fix invalid entries. For example, if the data entered in a field must be in all capital letters, the program should automatically convert lowercase characters to uppercase instead of waiting while the user enters mixed case, and then reporting an error and forcing the user to reenter the data.

Input validation reaches beyond the individual keystroke and field. It is also important to validate data at the form level. Input validation schemes should make sure that all required fields on a form are completed properly. If you have several fields that must be filled with valid data before the record can be saved to the database, you must have a method for checking those fields before you allow the user to attempt to save the record.

Conditional input fields must be validated, too. A conditional field is slightly different from a required field. Conditional fields usually occur when a user has checked a Yes/No box and then must enter additional data to complete the process. For example, if the user indicates on a form that the customer requests all products to be shipped instead of picked up, input validation should make sure that valid data has been entered into the shipping address fields. Another example of conditional field validation is when entering a value in one field requires that the value in another field be within a certain range. For example, if the customer's credit limit is above $50,000, you must enter a valid credit-worthiness code of 5 or above. In this case, the two fields must be checked against one another and verified before the user can save the record to the database.

As you can see from the preceding examples, input validation is more than just making sure the data entered in a field is correct. Input validation should be viewed as a set of rules to ensure that quality data is entered into the system. Before you begin writing your data entry forms, you should spend time developing a comprehensive set of validation rules. Once you develop these rules, you are ready to start creating your data entry form.

### Common Input Validation Rules

Almost every field in your database requires some type of input validation. Before you design your form, put together a list of all the fields you need on the form and answer the following questions for each input field:

- Must data be entered in the field? (Is it a required field?)

- What characters are valid/invalid for this field? (Numeric input only, capital letters only, no spaces allowed, and so on.)

- For numeric fields, is there a high/low range limit? (Must be greater than zero and less than 1000, can't be less than 100, and so on.)

- Is there a list of valid values for this field? (Can user enter only Retail, Wholesale, or Other; Name must already be in the Customer table, and so on.)

- Is this a conditional field? (If users enter Yes in field A, they must enter something in field C.)

Even though each data entry form is unique, you can use some general guidelines when putting together input validation schemes.

- If possible, limit keystrokes to valid values only. For example, if the field must be numeric, don't allow the user to enter character values. If spaces are not allowed, make sure the space bar is disabled. Help the user by limiting the kinds of data that can be entered into the field.

- Limit input choices with lists. If there is a limited set of valid inputs for a field, give the user a pick list or set of radio buttons to choose from.

- Inform the user of range limits. If a field has a high or low range limit, tell the user what the limits are.

- Point out required fields on a form. Mark required fields with a leading asterisk (*) or some other appropriate character. Possibly change the background color of required fields.

- Group conditional fields together on the form. If entering Yes in one field means that several other fields must be completed, put the additional fields close to the Yes/No field to help the user. Keep conditional fields of this type disabled until the Yes/No flag has been set. This helps the user see that new fields must be entered.

### Field-Level Validation

The first level of validation is at the field level. This is the place where you can make sure the user is entering the right characters in the field, entering the data into the field in the proper format, and entering a valid value based on a list of possible choices.

For the rest of this section, you will be building a sample application that illustrates the various input validation methods this chapter covers. If you haven't done so already, start up Visual Basic 5 and create a new Standard EXE project. Set the caption of the form to Input Validation and the Name of the form to frmValidation. Set the project Name property to Validation and save the form as `VALIDATION.FRM`; save the project as `VALIDATION.VBP`.

#### Filtering Keyboard Input

One of the easiest ways to perform input validation is to filter keyboard input. Filtering keyboard input requires capturing the keystrokes of the user before they appear on the screen and filtering out the keystrokes you do not want to appear in the input controls. You can filter invalid or undesirable keystrokes by creating a beep for the user each time an invalid key is pressed (for example, a beep each time a letter is pressed in a numeric field). You can also convert the invalid key to a valid one (for example, change lower case to upper case). Or you can simply ignore the keystroke completely and prevent the invalid values from ever appearing in the input control.

> TIP: Keep in mind that not all your potential users may be able to hear an audible beep and could become confused at the inability to input data. Windows 95 has several useful Accessibility Options that you may want to review, including the use of message boxes for hearing-impaired users.

For the first keyboard filtering example, you set up a textbox control that accepts only numerals zero through nine. First, add a label control and a textbox control to the form. Set the caption property of the label control to Numbers. Set the Name property of the textbox control to txtNumber and set the text property to blank. Your form should resemble the one in Figure 5.1.
Figure 5.1. Adding the Numbers input control.

Save and run the program. You can enter any type of data in the textbox that you wish--numbers, letters, spaces, and so on. Now you add a small bit of code that filters out all but the numerals zero through nine. You do this by using the textbox control `KeyPress` event.

The `KeyPress` event occurs each time a user presses a key while the field has the focus. Each time a key is pressed while the cursor is in the textbox control, the ASCII value of the key is sent to the `KeyPress` event where you can evaluate it and act accordingly.

> NOTE: Each key on the keyboard has an ASCII (American Standard Code for Information Interchange) numeric value. Your Visual Basic 5 documentation has a list of the ASCII codes for each key on the keyboard.

In this example, you want to ignore any keystroke that is not a 0, 1, 2, 3, 4, 5, 6, 7, 8, or 9. To do this, you need to add a small bit of code (see Listing 5.1) to the `KeyPress` event of the txtNumbers textbox.

### Listing 5.1. Limiting data entry in the Keypress event.

    Private Sub txtNumber_KeyPress(KeyAscii As Integer)
        `
        Dim strValid As String
        `
        strValid = "0123456789"
        `
        If InStr(strValid, Chr(KeyAscii)) = 0 Then
            KeyAscii = 0
        End If
        `
    End Sub

In Listing 5.1, you declared a string variable that holds the list of valid keys. The next line loads the string with the valid keys for this field, and the next line checks to see whether the key pressed is in the string of valid keys. It does this by converting the numeric value passed by Visual Basic 5 in the `KeyAscii` parameter (the ASCII value of the key pressed) into a readable character using the Visual Basic 5 `Chr` function and searching for the result in the list of valid keys in the cValid string. If the key pressed is not in the cValid string, the keystroke is set to 0. Setting the keystroke to 0 is telling Visual Basic 5 to pretend nothing was ever typed!

Now save and run the program. No matter what keys you type, only the numerals 0 through 9 appear in the textbox. You have filtered out everything but numerals. You may also notice that keystrokes, such as the backspace and delete keys, no longer work! You've told Visual Basic 5 to ignore them. You can fix that by adding a statement that checks to see whether the keystroke is a control code. Control codes are used in Visual Basic 5 to indicate that the key the user pressed was not a printable character but a keyboard control character. Common control characters are the Escape key, the Return key, the Backspace key, and so on.

You can also add any other characters to the validity list if you like. For example, you probably want to be able to enter a minus sign, a plus sign, and a decimal point in this number field. To do this, all you need to do is add those three characters to the cValid string. Your program code should now look like Listing 5.2.

### Listing 5.2. The KeyPress event with control characters.

    Private Sub txtNumber_KeyPress(KeyAscii As Integer)
        `
        Dim strValid As String
        `
        strValid = "0123456789+-."
        `
        If KeyAscii > 26 Then ` if it's not a control code
            If InStr(strValid, Chr(KeyAscii)) = 0 Then
                KeyAscii = 0
            End If
        End If
        `
    End Sub

Notice that in Listing 5.2, you first tested to see whether the key pressed was greater than 26. ASCII code 26 is the last Visual Basic 4 control code. The routine in Listing 5.2 now skips over filtering of control codes. When you save and run the program, you can pass the plus, minus, and decimal point characters into the textbox, too.

Now let's create validation code that accepts only uppercase characters. This is a bit trickier. Instead of ignoring lowercase input, you convert it to upper case, and then pass it through to the textbox.

First add another label and textbox control. Set the label caption to Uppercase. Set the Name property of the textbox to txtUpper and set the text property to blank. Your form should look like the one in Figure 5.2.
Figure 5.2. Adding the Uppercase control and conversion to the form.

The code needed for the txtUpper `KeyPress` event is in Listing 5.3. Even though there's only one line of code in this routine, there's a lot going on. This line of code (reading from the inside function outward) first coverts the Keyascii value into a printable character, converts that character to upper case, and then converts the character back to an ASCII numeric value. Notice that instead of setting the Visual Basic 5 KeyAscii parameter to zero (discarding it), this routine converts it to an uppercase value. This works no matter what key is pressed.

### Listing 5.3. The KeyPress event to force letters to uppercase.

    Private Sub txtUpper_KeyPress(KeyAscii As Integer)
        `
        KeyAscii = Asc(UCase(Chr(KeyAscii))) ` change to uppercase
        `
    End Sub

When you save and run the program, you see that any letter key you enter converts to an uppercase letter and passes through to the textbox.

The two types of keyboard filters illustrated here (discard or convert) can be combined to form a powerful input validation tool. Let's create a validation example that allows only uppercase letters A through Z, or numerals 0 through 9--no spaces or any other characters.

First add a new label/textbox control pair. Set the label caption property to Combined. Set the textbox Name property to txtCombined and the text property to blank. Refer to Figure 5.3 for positioning and sizing information.
Figure 5.3. Adding the Combined control to the form.

Listing 5.4 shows how to combine a check against a valid list and a conversion of keystrokes into a single input validation.

### Listing 5.4. A single KeyPress event to check for valid entry and force uppercase.

    Private Sub txtCombined_KeyPress(KeyAscii As Integer)
        `
        Dim strValid As String
        `
        strValid = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        `
        KeyAscii = Asc(UCase(Chr(KeyAscii)))
        `
        If KeyAscii > 26 Then
            If InStr(strValid, Chr(KeyAscii)) = 0 Then
                KeyAscii = 0
            End If
        End If
        `
    End Sub

#### Input Masking

It is very common to have fields on your form that require special input formats. Examples of special formats would be telephone numbers, Social Security numbers, hour/minute time entry, and so on. Visual Basic 5 ships with a bound data control that handles special input and display formatting--the MaskedEdit control. The MaskedEdit control works like the standard Visual Basic 5 textbox control, with a few added properties that make it a powerful tool for your input validation arsenal.

Let's add a phone number input field to the form. Add a new label to the form and set its caption property to Phone. Now add a MaskedEdit control to the form. Set its Name property to mskPhone, the Mask property to (###) ###-#### and the PromptInclude property to False.

> TIP: It is essential that you set the PromptInclude property to False when using the MaskedEdit control as a bound control. If the PromptInclude property is set to True, you get a database error each time you add a new record to the table or attempt to save or read a record that has a null value in the data field linked to the MaskedEdit bound control.

Your form should resemble Figure 5.4.
Figure 5.4. Adding the MaskedEdit Phone control to the form.

You do not need to add any additional filtering to the control because the MaskedEdit control makes sure that only digits are entered and that the input is limited to 10 digits formatted as a standard U.S. phone number.

Save and run the program. You can see that when the control is initialized, the phone number mask is displayed. When the MaskedEdit control receives the focus, a series of underlines appear as an input guide for the user. The underlines disappear when control is given to an object other than the MaskedEdit control.

> NOTE: The formatting characters of the MaskedEdit control are not saved to the database field when the PromptInclude property is set to False. This means that in the previous example, only the phone number digits would be saved to the data table, not the parentheses or the dash.

The Visual Basic 5 MaskedEdit control offers an extensive set of input masking tools. It ships with several input masks predefined, including dollar amounts, U.S. phone numbers, and several date and time formats. To view these formats, select the MaskedEdit control, click the right (alternate) mouse button, and select Properties from the menu that appears. You find the formats on the General tab.

You can also create custom input format masks for inventory part numbers, e-mail addresses, and so on. Although we won't cover all the possibilities here, there is one other MaskedEdit format option that you illustrate on your form in this lesson because it is very useful when displaying dollar amounts.

The MaskedEdit control gives you the power to add a display mask in addition to an input mask. Up to this point, you have been using the input mask capabilities of the MaskedEdit control. Now let's add a control that shows the display capabilities, too.

Add another label control and another MaskedEdit control to the form. Set the label caption property to Dollars. Set the MaskedEdit control Name property to mskDollars and the format property to `$#,##0.00;($#,##0.00)`.

> TIP: The MaskedEdit display property actually has three parts, each separated by the semicolon (;). Part one determines how positive values are displayed. Part two determines how negative values are displayed. Part three determines how zero values are displayed.

This property affects the display of the data, not the input, so you do not see any input guides when you set the format property or when you save and run the program. Your form should look like the one in Figure 5.5.
Figure 5.5 Adding the Dollar control to the form.

Now run the program and enter a numeric value in the Dollars textbox. When you leave the textbox to go to another control, you see the MaskedEdit control format the display of the amount you entered. Your screen should resemble Figure 5.6. Please note that two decimal places always appear to the right of the decimal.
Figure 5.6. The display results of the MaskedEdit control.

####
Validation Lists

One of the most common field-level input validation routines is the use of a validation list. The list contains a set of possible inputs for the field--usually displayed in a list box or a drop-down list control. Instead of having to guess at a valid value, the user can simply scan the list and click on the proper choice. Validation lists require a bit more programming to use, but the rewards far exceed the effort. Using validation lists virtually guarantees that you will not have a data entry error occur on the input field.

Before you can use a validation list for input validation, you must first have a list. It is usually a good idea to load any validation lists you need for a form at the time you load the form. This means that validation lists should be loaded in the `Form_Load` event. Let's add some code to your project that loads a drop-down list box with a list of possible customer types.

First add another label and a drop-down combo box control to the form. Set the label caption to Customer Type, and set the drop-down combo box Name property to cboCustType and the Style property to 2--DropDown List. Your form should look like the one in Figure 5.7.
Figure 5.7. Adding the DropDown List control to the form.

> NOTE: You can't change the height of the combo box control in Visual Basic 5. It is set at 315 twips and cannot be updated.

Now add Listing 5.5 to load the list box with valid values.

### Listing 5.5. The Form_Load event to load a list box.

    Sub Form_Load ()
       `
       ` load dropdown list box
       cboCustType.AddItem "Retail"
       cboCustType.AddItem "Wholesale"
       cboCustType.AddItem "Distributor"
       cboCustType.AddItem "Other"
    End Sub

In Listing 5.5, you are adding values directly to the list using program code. Each `AddItem` method adds an additional valid selection to the list. You could also load the control with values from a data table. This would give you a more dynamic list of valid values. For now, stick to the direct load example here; later in this book, you add validation lists loaded from data tables.

Now save and run the program. You can now click the down arrow of the drop-down list box and see the list of valid values. Now the user can't help but pick a correct item for the input field.

Notice that when you first start the form, the combo box shows an empty value. This indicates no selection has been made. You can add some code to your form that selects a default item from the list, too. Add the following line of code to the `Form_Load` event:

    cboCustType.ListIndex = 0 ` set default value

Now when the form starts, you see the first value in the list has already been selected.

Up to this point, you have been developing methods for handling field-level validation. The next step is to add validation routines at the form level.

### Form-Level Validation

Form-level validation is an essential part of designing a good validation scheme for your form. Although many input errors can be caught and corrected at the field level, there are several validation steps that can only be performed well at the form level.

Although field-level validation is performed at the time a key is pressed or at the time a field loses focus, form-level validation is performed at the time the user presses Enter, or clicks the OK or Save button. These are validations that are done after the user has entered all fields, but before any attempt is made to store the values to a data table.

Form-level validation can be divided into three groups:

- Independent content validation

- Required field validation

- Dependent field validation

Let's look at each type of form-level validation.

#### Independent Content ValidationHigh/Low Ranges

A common form-level validation routine is one that checks the upper and lower values of a numeric entry and makes sure the value is within the high/low range. This is very useful on all types of forms that have dollar amounts or unit count minimum and maximum values.

> NOTE: Although it might seem that this kind of validation should be done at the field level, it is better to perform it at the form level. If a user enters a value that is not within the acceptable range, the field that contains the invalid data must be given focus so that the user can correct the entry. Setting the control's focus is best done outside of any other control's `GotFocus` or `LostFocus` event. Also, because a user can use the mouse to skip over any field on the form, placing independent content validation routines within the controls' events means that users may skip important validation steps in the process.

To set up the form-level validation, first add a single command button to the form. Set its Name property to cmdOK and its caption property to &OK. Now add another label/textbox pair to the form. Set the label caption to High/Low. Set the textbox Name property to txtHighLow and the text property to blank. Refer to Figure 5.8 for sizing and placement information.
Figure 5.8. Adding the High/Low control and OK button to the form.

Next add Listing 5.6 to the `cmdOK_click` event.

### Listing 5.6. The form-level validation routine to check for values in a range.

    Private Sub cmdOK_Click()
        `
        Dim intHigh As Integer
        Dim intLow As Integer
        Dim strHighLowMsg As String
        `
        intHigh = 100
        intLow = 1
        strHighLowMsg = "High/Low field must contain a value between " & _
           CStr(intLow) & " and " & CStr(intHigh)
        `
        If Val(txtHighLow) < intLow Or Val(txtHighLow) > intHigh Then
            MsgBox strHighLowMsg
            txtHighLow.SetFocus
        End If
        `
    End Sub

The code in Listing 5.6 establishes the integer variables for the high and low in the range, sets them to 100 and 1 respectively, and then checks the value entered into the txtHighLow text control. If the value is out of the allowed range, a message is displayed, and the input cursor is moved back to the field that contains the invalid data. Notice that the message not only tells the user that the data is invalid, it also tells the user what values are acceptable. If the data entered is within range, the program exits normally.

Now save and run the program. If you skip to the OK button without entering data or enter data outside the allowed range, you see the validation message.

#### Independent Content ValidationMin/Max Field Lengths

Another common form-level validation step is to make sure that character strings meet the minimum or maximum length requirements. This is done in the same way numeric values are checked for high and low ranges.

Let's add input validation to ensure that the Uppercase textbox you placed on the form earlier is no longer than 10 characters, and at least 3 characters in length. You just need to add the code in Listing 5.7 to the `cmdOK_click` event that checks the txtUpper field for length.

### Listing 5.7. The form-level validation routine to check the length of fields and a valid range of values.

    Private Sub cmdOK_Click()
        `
        Dim intHigh As Integer
        Dim intLow As Integer
        Dim strHighLowMsg As String
        `
        Dim intMinLen As Integer
        Dim intMaxLen As Integer
        Dim strMinMaxMsg As String
        `
        Dim blnOK As Boolean
        `
        intHigh = 100
        intLow = 1
        strHighLowMsg = "High/Low field must contain a value between " & _
           CStr(intLow) & " and " & CStr(intHigh)
        `
        intMinLen = 3
        intMaxLen = 10
        strMinMaxMsg = "Upper field must be between " & _
           CStr(intMinLen) & " and " & CStr(intMaxLen) & " long."
        `
        blnOK = False
        `
        ` check high/low field
        If Val(txtHighLow) < intLow Or Val(txtHighLow) > intHigh Then
            MsgBox strHighLowMsg
            blnOK = False
            txtHighLow.SetFocus
        End If
        `
        ` check upper field
        If Len(txtUpper) < intMinLen Or Len(txtUpper) > intMaxLen Then
            MsgBox strMinMaxMsg
            blnOK = False
            txtUpper.SetFocus
        End If
        `
        ` set if all passed
        If blnOK = True Then
            Unload Me
        End If
        `
    End Sub

In Listing 5.7, you added variables for the minimum and maximum length of the entry field, a new message variable, and a flag variable to show that all validation steps passed. Notice that you changed the structure of the validation steps from a simple `If_Then_Else` to a series of `If_Then` routines. If the validation does not pass, a flag is set to make sure the form does not unload.

Save and run the form to test the validation rule. You see that now both form-level validation rules must be met before the form unloads.

> NOTE: The txtUpper field has both field-level and form-level validation rules applied to it. The field-level routine executes when data is entered into the field. The form-level validation routine executes when this data record is saved. It is perfectly acceptable, and sometimes recommended, to have both field-level and form-level validation for the same control.

#### Required Fields

Almost every form has at least one field that is required input. Some forms may have several. Checking for required input fields is done at the form level. Let's add code at the `cmdOK_click` event that makes sure that users fill out the Combined field every time.

All you need to do is validate that the txtCombined field contains valid data. Listing 5.8 shows how this is done.

### Listing 5.8. The form-level validation routine to check for required fields.

    Private Sub cmdOK_Click()
        `
        Dim intHigh As Integer
        Dim intLow As Integer
        Dim strHighLowMsg As String
        `
        Dim intMinLen As Integer
        Dim intMaxLen As Integer
        Dim strMinMaxMsg As String
        `
        Dim blnOK As Boolean
        `
        intHigh = 100
        intLow = 1
        strHighLowMsg = "High/Low field must contain a value between " & _
           CStr(intLow) & " and " & CStr(intHigh)
        `
        intMinLen = 3
        intMaxLen = 10
        strMinMaxMsg = "Upper field must be between " & _
           CStr(intMinLen) & " and " & CStr(intMaxLen) & " long."
        `
        blnOK = False
        `
        ` check high/low field
        If Val(txtHighLow) < intLow Or Val(txtHighLow) > intHigh Then
            MsgBox strHighLowMsg
            blnOK = False
            txtHighLow.SetFocus
        End If
        `
        ` check upper field
        If Len(txtUpper) < intMinLen Or Len(txtUpper) > intMaxLen Then
            MsgBox strMinMaxMsg
            blnOK = False
            txtUpper.SetFocus
        End If
        `
        ` check the combined field
        If Len(Trim(txtCombined)) = 0 Then
            MsgBox "Combined field is a required field"
            blnOK = False
            txtCombined.SetFocus
        End If
        `
        ` set if all passed
        If blnOK = True Then
            Unload Me
        End If
        `
    End Sub

The only change you made is to check the length of the string in the txtCombined textbox. If the result is zero, an error message is displayed. Notice the use of the `Trim` function to remove any trailing or leading spaces from the txtCombined string. This makes sure that users who enter blank spaces into the field do not get past the validation step.

#### Conditional Fields

There are times when entering a value in one field of the form means that other fields on the form must also contain valid data. Fields of this type are called conditional fields. A good example of conditional field validation can be found in an order tracking system. For example, when a user enters Yes in the Ship to Site? field, he or she must then enter a valid value in the Shipping Address field. The Shipping Address field is a conditional field because its validation is based on the condition of the Ship to Site? field.

Now add a conditional validation to the project. Make the field CustType conditional to the field Upper. In other words, if the Upper field contains data, the CustType field must contain data. See Listing 5.9 for an example of how to do this.

### Listing 5.9. The form-level conditional validation routine.

    Private Sub cmdOK_Click()
        `
        Dim intHigh As Integer
        Dim intLow As Integer
        Dim strHighLowMsg As String
        `
        Dim intMinLen As Integer
        Dim intMaxLen As Integer
        Dim strMinMaxMsg As String
        `
        Dim blnOK As Boolean
        `
        intHigh = 100
        intLow = 1
        strHighLowMsg = "High/Low field must contain a value between " & _
           CStr(intLow) & " and " & CStr(intHigh)
        `
        intMinLen = 3
        intMaxLen = 10
        strMinMaxMsg = "Upper field must be between " & _
           CStr(intMinLen) & " and " & CStr(intMaxLen) & " long."
        `
        blnOK = False
        `
        ` check high/low field
        If Val(txtHighLow) < intLow Or Val(txtHighLow) > intHigh Then
            MsgBox strHighLowMsg
            blnOK = False
            txtHighLow.SetFocus
        End If
        `
        ` check upper field
        If Len(txtUpper) < intMinLen Or Len(txtUpper) > intMaxLen Then
            MsgBox strMinMaxMsg
            blnOK = False
            txtUpper.SetFocus
        End If
        `
        ` check the combined field
        If Len(Trim(txtCombined)) = 0 Then
            MsgBox "Combined field is a required field"
            blnOK = False
            txtCombined.SetFocus
        End If
        `
        ` check conditoinal upper/custtype fields
        If Len(Trim(txtUpper)) <> 0 And Len(Trim(cboCustType)) = 0 Then
            MsgBox "If Upper field conains data then " & _
              "the Customer Type field must also contain data"
            blnOK = False
            cboCustType.SetFocus
        End If
        `
        ` set if all passed
        If blnOK = True Then
            Unload Me
        End If
        `
    End Sub

Save and run the program. Now you must enter valid data in both fields before the form unloads. You have probably also found out that each time you click the OK button, all the form-level validation steps are performed. It is good programming practice to deliver all the validation results to the user at once. It can be very frustrating to fill out a form, receive an error message, and then fix the message, only to receive another one, and another one, and so on.

### Creating Your Own Validation Custom Control

The input validation routines you created today cover most of the situations you are likely to encounter when designing data entry forms. In fact, after you design one or two of these forms, you begin to see that you are writing the same validation code over and over again. Instead of repeatedly writing the same code, or even constantly performing cut, copy, and paste operations, Microsoft Visual Basic 5 gives you the power to create your own custom controls that have new properties and methods for input validation. Once you build these new controls, they can be used in all your data entry programs.

#### Creating a Custom Control

In this section you get a crash course on creating custom controls using Visual Basic 5.

> NOTE: There are a lot of possibilities and considerations when creating custom controls. In this section, you get only the basics. You can find excellent examples of how to create your own custom controls and associated property pages in the Visual Basic 5 online documentation. Look up "Controls" in the Online Books Index to see a list of topics related to using Visual Basic 5 to create custom controls.

The first step in creating a custom control is to open a Visual Basic 5 UserControl code module. This module is a bit like the standard Visual Basic BAS (pronounced bass, like the fish) modules and like the Class modules introduced in Visual Basic 4. Using the UserControl module allows you to create properties, methods, and events for your custom control. Once you complete the control code, you can compile the object into an ActiveX control (sometimes called an OCX control) that can be used in any ActiveX-compliant developer tool. You can use this control in Visual Basic (versions 4 and 5), C++, Access, and, in most cases, Microsoft Internet Explorer.

To create a new custom control, select File | New Project from the main menu and then select ActiveX Control from the list of default templates (see Figure 5.9).
Figure 5.9. Opening a Visual Basic BAS module.

Set the Name property of the UserControl module to VText (validated textbox) and set the project name to CustomValidation. Create a new directory for this project called CustomValidation. Save the UserControl file as `VTEXT.CTL` and the project as `CUSTOMERVALIDATION.VBP`. This is the framework for creating your custom validation control.

Creating a custom control is really easy. You need to build four primary sets of routines for a custom control:

- Properties

- Methods

- Events

- Maintenance routines

Although the first three types of code routines are fairly obvious, the fourth, maintenance routines, are new. When you create an ActiveX control, you need to write code that manages the setting and retrieving of property values during both design time and runtime. To do this, you need to write a few short routines to keep track of all the property settings for your control. You learn how to code each of these types of routines as you build your validation textbox.

#### Laying Out the Control and Building the Maintenance Routines

For this example, you only need one visual control on your custom control--the TextBox control. Place a single TextBox control on your UserControl form. Don't worry about its size or placement. You set all that up in code. After you add the TextBox control to the UserControl form space, it should look like the UserControl in Figure 5.10
Figure 5.10. Adding the TextBox control to the UserControl form space.

The next step is to add code that allows for resizing the control (at design time or runtime), and some initialization code that runs the first time the control is added to a Visual Basic form. First, add the code in Listing 5.10 to your project. This handles resizing of the control.

### Listing 5.10. Adding code to the UserControl_Resize event.

    Private Sub UserControl_Resize()
        `
        ` make textbox fill the control space
        `
        With Text1
            .Left = 1
            .Top = 1
            .Height = UserControl.Height
            .Width = UserControl.Width
        End With
        `
    End Sub

Now, each time the user resizes the control, the textbox stretches to fill the space.

Next you need to add a few lines of code to the `InitProperties` event. This code sets the default size of the control when it is placed on a form. Enter the code from Listing 5.11 into the `UserControl_InitProperties` event.

### Listing 5.11. Adding code to the UserControl_InitProperties event.

    Private Sub UserControl_InitProperties()
        `
        With UserControl
            .Width = 1200
            .Height = 300
        End With
        `
        Text1.Text = UserControl.Name
        `
    End Sub

Along with the `Resize` and `InitProperties` events, there are two other very important events that you must populate with code. These are the `ReadProperties` and `WriteProperties` events of the UserControl. These two events are used to keep track of design-time property settings. When you set properties during design time, Visual Basic must have a way to store them so that the runtime version of Visual Basic knows what they are.

Because we haven't defined any custom properties to remember, you won't be coding these two events until a bit later in the chapter. However, it is important that you remember to code the `Read` and `Write` property routines for each custom property you define for your control. If you fail to do this, you won't be able to access any of the design-time property values once your Visual Basic program starts running.

Now that the maintenance steps have been addressed, it's time to add some functionality to the new control.

#### Coding the KeyFilter Validations for the Custom Control

The first validation routine to add to the custom control is the key press filter routines. In this example control, you add key press filters for upper case, digits, and numeric data. These routines work very much like the key press routines you coded in the first part of this chapter. You need to create a few properties, some default values, and a couple of events to handle key press validation. Listing 5.12 shows the declarations for the key press routines. Add this code to the general declaration section of your UserControl module.

### Listing 5.12. Adding the KeyFilter declarations to the VText control.

    ` ------------------------------------------------
    ` KeyFilter values
    `
    ` Event list
    Public Event NumericError(KeyAscii)
    Public Event DigitsError(KeyAscii)
    `Default Property Values:
    Const m_def_UpperCase = 0
    Const m_def_Digits = 0
    Const m_def_Numeric = 0
    `Property Variables:
    Dim m_UpperCase As Boolean
    Dim m_Digits As Boolean
    Dim m_Numeric As Boolean
    Dim m_Text As String

The code in Listing 5.12 establishes two events that fire messages back to your Visual Basic program if an invalid key is pressed. This code also defines local storage for three Boolean properties and one string property. The three Boolean properties also have a default value defined. You add the new Public properties in just a moment.

First, add some code to the `InitProperties` event to set the default values for these new properties. The code in Listing 5.13 shows the modified `UserControl_InitProperties` event.

### Listing 5.13. Adding KeyFilter defaults to the InitProperties event.

    Private Sub UserControl_InitProperties()
        `
        ` initialize control values
        `
        With UserControl
            .Width = 1200
            .Height = 300
        End With
        `
        Text1.Text = UserControl.Name
        `
        ` KeyFilter properties
        m_UpperCase = m_def_UpperCase
        m_Digits = m_def_Digits
        m_Numeric = m_def_Numeric
        `
    End Sub

Now it's time to create the new properties to match the local storage variables. To create properties for your control, select Tools | Add Procedure from the main menu. This brings up the dialog box you use to add all your properties, methods, and events. Enter UpperCase as the name, select the Property and Public radio buttons, and then press OK (see Figure 5.11).
Figure 5.11. Adding a new property to the VText control.

Notice that Visual Basic has created two special functions in your UserControl module. The `Property Get` routine is used to allow others to "get" the value of your published property. The `Property Let` routine is used to "let" others send you a value to store in the property. For this chapter, you create all your custom control properties this way.

> TIP: Visual Basic 5 ships with an ActiveX Control Wizard that helps you create all the properties, methods, and events for your control. Although you'll not be using this wizard in this chapter, you can learn more about this and other aspects of creating custom controls by using the search word "controls" in the Visual Basic Book Online documents.

You need to modify these default routines to match your published properties. The code in Listing 5.14 shows you how to code the `Let` and `Get` routines for the UpperCase property.

### Listing 5.14. Coding the Let and Get routines for the UpperCase property.

    Public Property Get UpperCase() As Boolean
        UpperCase = m_UpperCase
    End Property

    Public Property Let UpperCase(ByVal New_UpperCase As Boolean)
        m_UpperCase = New_UpperCase
        PropertyChanged "UpperCase"
    End Property

The code in Listing 5.14 is typical of property-handling routines. The main job of these routines is to transfer values between the control and its "host" program. Note the use of the `PropertyChanged` method. This method informs Visual Basic that the property has been updated and forces Visual Basic to update the design-time properties Window and fire off the event needed to update the stored properties.

You need to declare Public properties for the Digits and Numeric storage values. Use the same process you used for the UpperCase property. Select Tools | Add Procedure; enter the property name (for example, Digits); select the Property and Public option buttons, and then press Enter. Do this for both the Digits and Numeric properties.

Listing 5.15 shows the code to add to the Digits property `Let` and `Get` routines.

### Listing 5.15. Coding the Digits property Let and Get routines.

    Public Property Get Digits() As Boolean
        Digits = m_Digits
    End Property

    Public Property Let Digits(ByVal New_Digits As Boolean)
        m_Digits = New_Digits
        PropertyChanged "Digits"
    End Property

After adding the Digits code, add the code from Listing 5.16 to the Numeric property `Let` and `Get` routines.

### Listing 5.16. Coding the Numeric property Let and Get routines.

    Public Property Get Numeric() As Boolean
        Numeric = m_Numeric
    End Property

    Public Property Let Numeric(ByVal New_Numeric As Boolean)
        m_Numeric = New_Numeric
        PropertyChanged "Numeric"
    End Property

There is one final property you need to create--the Text property. Unlike the three other properties, which are unique to this control, the Text property is "mapped" to the Text property of the TextBox control that appears as part of the VText control. Even though the Text property exists for the TextBox, you must publish the Text property for the VText control before users can access any data in the Text1.Text control. After using Tools | Add Procedure to create the Public property Text, enter the code from Listing 5.17 into the corresponding `Let` and `Get` routines for the Text property.

### Listing 5.17. Coding the Text property Let and Get routines.

    Public Property Get Text() As Variant
        Text = Text1.Text
    End Property

    Public Property Let Text(ByVal vNewValue As Variant)
        Text1.Text = vNewValue
    End Property

Finally, you need to set the attributes of this property so that it can be used within database forms. Select Tools | Procedure Attributes to bring up the attributes dialog.

Select the Text property, select the Advanced button, and place a check mark in all the Data Bindings fields at the bottom of the form. This adds the DataField and DataSource properties to the VText control (see Figure 5.12).
Figure 5.12. Setting the Data Bindings of a custom property.

There is one more set of routines you need to code before your property handling is complete. You need to add code to the `WriteProperties` and `ReadProperties` events of the UserControl. This code is executed each time the control moves from design time to runtime. Listing 5.18 shows the code for the `WriteProperties` event. Add this to your UserControl modules.

### Listing 5.18. Coding the WriteProperties event of the VText control.

    Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
        `
        ` save design-time property values
        `
        With PropBag
            Call .WriteProperty("UpperCase", m_UpperCase, m_def_UpperCase)
            Call .WriteProperty("Digits", m_Digits, m_def_Digits)
            Call .WriteProperty("Numeric", m_Numeric, m_def_Numeric)
            `
            Call .WriteProperty("Text", Text1.Text, UserControl.Name)
        End With
        `
    End Sub

All read and write operations are performed using the PropBag ("Property Bag") object. This object contains the correct settings for all the properties at any given time. Notice that the `WriteProperty` method of the PropBag object takes three parameters: property name, current value, and default value. The default value is used if the current value is set to null.

> TIP: Although the default value is an optional parameter for the `WriteProperty` method, you should use it. Values are saved only if they are different from the default value. If you include default values for all your properties, you can reduce the size of the file needed to store your custom properties.

Next, add the code in Listing 5.19 to the `UserControl_ReadProperties` event. This ensures that Visual Basic runtime loads the property values stored by in the `WriteProperties` event by Visual Basic design time.

### Listing 5.19. Coding the ReadProperties event for the VText control.

    Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
        `
        ` read design-time property values
        `
        With PropBag
            m_UpperCase = .ReadProperty("UpperCase", m_def_UpperCase)
            m_Digits = .ReadProperty("Digits", m_def_Digits)
            m_Numeric = .ReadProperty("Numeric", m_def_Numeric)
            `
            Text1.Text = .ReadProperty("Text", UserControl.Name)
        End With
        `
    End Sub

The `ReadProperty` method uses two parameters (property name and default value) and returns a value. This return value should be placed in the local storage variable for the Public property.

Now that all the properties are built, you need to add the code that actually validates user input. For this control, you create a single Public method that checks all three key press validation rules. Select Tools | Add Procedure and enter KeyValidate as the name. Select the Function and Public option buttons before pressing the OK button. Now add the code from Listing 5.20 to the project.

### Listing 5.20. Coding the KeyValidate method.

    Private Function KeyValidate(intKeyValue As Integer) As Integer
        `
        ` check value against rules
        `
        Dim strNumeric As String
        Dim strDigits As String
        `
        strNumeric = "0123456789+-."
        strDigits = "0123456789"
        `
        ` convert to uppercase
        If m_UpperCase = True Then
            intKeyValue = Asc(UCase(Chr(intKeyValue)))
        End If
        `
        ` check for digits
        If m_Digits = True Then
            If InStr(strDigits, Chr(intKeyValue)) = 0 Then
                intKeyValue = 0
                RaiseEvent DigitsError(intKeyValue)
            End If
        End If
        `
        ` check for numerics
        If m_Numeric = True Then
            If InStr(strNumeric, Chr(intKeyValue)) = 0 Then
                intKeyValue = 0
                RaiseEvent NumericError(intKeyValue)
            End If
        End If
        `
        KeyValidate = intKeyValue
        `
    End Function

The code in Listing 5.20 combines code from several of the `KeyPress` events you coded in the first part of this chapter. The only real difference is the use of the `RaiseEvent` keyword. This keyword is used to send a message back to the host program using this control. In this case, the messages are sent if an invalid key has been entered. Along with the message, the invalid key's ASCII value is returned. You see this event when you test the control a bit later in this chapter. After adding the `KeyValidate` routine, you need to add some code to the project to call `KeyValidate` each time a key is pressed. Listing 5.21 shows the code to add to the `Text1_KeyPress` event.

### Listing 5.21. Coding the Text1_KeyPress event.

    Private Sub Text1_KeyPress(KeyAscii As Integer)
        `
        KeyAscii = KeyValidate(KeyAscii)
        `
    End Sub

This code should look familiar. Now, each time the user presses a key in the textbox, the `KeyValidate` function is called. If the key press is invalid, an error event message is fired off to the host program.

That's all the coding you need to do to create a custom control that filters key presses. Save the UserControl module (`VTEXT.CTL`) and the project file (`CUSTOMVALIDATION.VBP`) and close all the windows associated with this project. You should now see a new control appear in the toolbox. This is your new custom control. You use it in a test form to make sure you've built all the code correctly.

#### Testing the KeyFilter Options of the VText Control

To test the new control, you need to add a new project to this project group. Select File | Add Project and select the Standard EXE project template from the New Project tab. Locate the new VText control in your toolbox and add it to the blank form (see Figure 5.13).
Figure 5.13. Adding the VText control to a blank form.

Set the Name property of the new control to vtxUpper, the UpperCase property to True, and the Text property to "". Save the form as `TEXT.FRM` and the project as `TEST.VBP` and run the project. When you type in the textbox, all your input is converted to uppercase (see Figure 5.14).
Figure 5.14. Testing the VText control's UpperCase option.

Now add two more VText controls to the form. Set the Name property of one to vtxDigits, the Digits property to True, and the Text property to "". For the other control, set the name property to vtxNumeric, the Numeric property to True, and the Text property to "". You can also add three labels to the form as shown in Figure 5.15.
Figure 5.15. Laying out the Test form.

After laying out the form, you can add two lines of code to the project. These lines of code display a message box each time the user presses an invalid key inside each of the VText controls. Add the code in Listing 5.22 to the project.

### Listing 5.22. Coding the error events for VText.

    Private Sub vtxDigits_DigitsError(KeyAscii As Variant)
        `
        MsgBox "Enter Digits only! `0123456789'"
        `
    End Sub

    Private Sub vtxNumeric_NumericError(KeyAscii As Variant)
        `
        MsgBox "Enter Numeric data only! `0123456789.+-'"
        `
    End Sub

Now save and run the test project. When you attempt to enter invalid data in the vtxDigits or the vtxNumeric boxes, you see the message box reminding you of the valid keystrokes for that control.

That ends the crash course on creating custom controls. The next three sections show you how to add other validation options to your VText control.

#### Adding Range-Checking Options to the VText Control

One of the other validation options you can add to the VText control is range checking. By adding min and max properties and a property to toggle the range checking on or off, along with a single validation method and an error event, you can expand the power of the VText control considerably.

Listing 5.23 shows the code you need to add to the general declaration section of the VText UserControl. These code declarations define the events, methods, and properties needed to add range checking to your custom control.

### Listing 5.23. Declaring range-checking properties, events, and methods.

    ` --------------------------------------------------
    ` CheckRange values
    `
    ` event list
    Public Event InRangeError(vValue)
    ` Properties
    Dim m_CheckRange As Boolean
    Dim m_HighValue As Variant
    Dim m_LowValue As Variant
    ` Default Values
    Const m_def_CheckRange = 0
    Const m_def_LowValue = 0
    Const m_def_HighValue = 0

Next you need to use the Tools | Add Procedures dialog to create three new properties. Make CheckRange a Boolean Public property. Then make HighValue and LowValue Variant Public properties.

> TIP: You might think that HighValue and LowValue should be declared numeric types, that is, Integer or Long. However, this may not be the best idea. Although Integers or Longs might be OK, what if you declare the property as Integer and want to set it to a number greater than 33000? Although a Long data type could accept the higher number, what if you wanted to set the property from within a code segment using an integer variable? Using a Variant data type and performing type checking gives you the greatest degree of flexibility.

The code in Listing 5.24 shows you how to populate the Property `Let` and `Get` routines for the three new properties.

### Listing 5.24. Coding the Let and Get property routines.

    Public Property Get CheckRange() As Boolean
        CheckRange = m_CheckRange
    End Property

    Public Property Let CheckRange(ByVal vNewValue As Boolean)
        m_CheckRange = vNewValue
        PropertyChanged "CheckRange"
    End Property

    Public Property Get LowValue() As Variant
        `
        LowValue = m_LowValue
        `
    End Property

    Public Property Let LowValue(ByVal vNewValue As Variant)
        `
        If IsNumeric(vNewValue) = False Then
            Error 380 ` invalid property value
        Else
            m_LowValue = vNewValue
            PropertyChanged "LowValue"
        End If
        `
    End Property

    Public Property Get HighValue() As Variant
        `
        HighValue = m_HighValue
        `
    End Property

    Public Property Let HighValue(ByVal vNewValue As Variant)
        `
        If IsNumeric(vNewValue) = False Then
            Error 380 ` invalid property
        Else
            m_HighValue = vNewValue
            PropertyChanged "HighValue"
        End If
        `
    End Property

Notice how the HighValue and LowValue `Let` routines handle the incoming value. First, the parameter is checked to see if it can pass as a numeric value. If not, an error is invoked. If the parameter can be converted into numeric data, the value is stored and the `PropertyChanged` method is used to mark the item.

> WARNING: When reporting an error from within an ActiveX control, it is always better to "raise" an error using the Error keyword than to display a custom message box. Custom message boxes can get in the way of the host program interface at either runtime or design time. However, using the `Error` method to report an error is handled appropriately by the host development tool.

After adding the new properties to the file, you need to update the `InitProperties` event to reflect the new default values for the range-checking properties. Listing 5.25 shows how this is done. Add this code to the end of the `InitProperties` event.

### Listing 5.25. Adding the range-checking properties to the InitProperties event.

    `
        ` InRange properties
        m_CheckRange = m_def_CheckRange
        m_HighValue = m_def_HighValue
    m_LowValue = m_def_LowValue

Next you need to update the `ReadProperties` and `WriteProperties` events of the UserControl. Listing 5.26 has the modified `ReadProperties` event. Refer to Listing 5.27 for the most recent changes to the `WriteProperties` event.

### Listing 5.26. Modifying the ReadProperties event.

    Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
        `
        ` read design-time property values
        `
        With PropBag
            m_UpperCase = .ReadProperty("UpperCase", m_def_UpperCase)
            m_Digits = .ReadProperty("Digits", m_def_Digits)
            m_Numeric = .ReadProperty("Numeric", m_def_Numeric)
            `
            m_CheckRange = .ReadProperty("CheckRange", m_def_CheckRange)
            m_LowValue = .ReadProperty("LowValue", m_def_LowValue)
            m_HighValue = .ReadProperty("HighValue", m_def_HighValue)
            `
            Text1.Text = .ReadProperty("Text", UserControl.Name)
        End With
        `
    End Sub

###
Listing 5.27. Modifying the WriteProperties event.

    Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
        `
        ` save design-time property values
        `
        With PropBag
            Call .WriteProperty("UpperCase", m_UpperCase, m_def_UpperCase)
            Call .WriteProperty("Digits", m_Digits, m_def_Digits)
            Call .WriteProperty("Numeric", m_Numeric, m_def_Numeric)
            `
            Call .WriteProperty("CheckRange", m_CheckRange, m_def_CheckRange)
            Call .WriteProperty("LowValue", m_LowValue, m_def_LowValue)
            Call .WriteProperty("HighValue", m_HighValue, m_def_HighValue)
            `
            Call .WriteProperty("Text", Text1.Text, UserControl.Name)
        End With
        `
    End Sub

Now you need to write the actual range validation routine. Use the Tools | Add Procedure dialog to create a method called `InRangeValidate` with the `Sub` and `Public` options. Add the code from Listing 5.28 to the method.

### Listing 5.28. Coding the InRangeValidate method.

    Public Sub InRangeValidate()
        `
        ` check value within range
        `
        Dim varCheckValue As Variant
        `
        If IsNumeric(Text1.Text) = False Then
            RaiseEvent InRangeError(varCheckValue)
            Exit Sub
        Else
            varCheckValue = Text1.Text
        End If
        `
        If varCheckValue < m_LowValue Or varCheckValue > m_HighValue Then
            RaiseEvent InRangeError(varCheckValue)
        End If
        `
    End Sub

Notice that the first check is to make sure that the data entry is really numeric data. Then the numeric data is compared to the min and max values set in the properties. If the comparison fails, a new error event is fired off to the host environment.

Finally, to call this routine automatically within the control, place the following code behind the `Lost_Focus` event for the Text1 control:

    Private Sub Text1_LostFocus()
        `
        If CheckRange = True Then InRangeValidate
        `
    End Sub

That's it for the range-checking features of the VText control. Save the control (`VTEXT.CTL`) and the project (`CUSTOMVALIDATION.VBP`) before you test your new features.

#### Testing the InRange Options of the VText Control

Select the `TEST` project again and add a new vText control to the form along with an OK button (see Figure 5.16).

Now set the following properties on the new VText control:

- Name vtxInRange

- CheckRange True

- Digits True

- HighValue 100

- LowValue 50

- Text ""

Figure 5.16. Laying out the range-checking controls on the test form.

After setting these values, add the code in Listing 5.29 to the test form.

### Listing 5.29. Adding code to handle the InRange properties.

    Private Sub cmdOK_Click()
        If vtxInRange.CheckRange = True Then
            vtxInRange.InRangeValidate
        End If
    End Sub

    Private Sub vtxInRange_InRangeError(vValue As Variant)
        `
        MsgBox "InRange Error Reported!"
        `
    End Sub

Notice the addition of the validation code behind the cmdOK button. This is required, even though you added built-in validation within the control itself. Remember that the `Lost_Focus` event was used to trigger the `InRangeValidate` routine. What if the user loads the form, enters data in every field but this one and then proceeds by pressing OK? As you may have guessed, nothing happens since the vtxInRange control never received focus. This is the essence of form-level validation.

Save and run the project. You see an error whenever your input range is out of bounds.

#### Adding CheckSize Options to the VText Control

Another important form-level validation option is to check the length of the input string. You need three new properties: CheckSize (Boolean), MinLength (integer), and MaxLength (integer). The code in Listing 5.30 shows the modifications to the general declaration section of the UserControl.

### Listing 5.30. Modifying the declaration section of VText.

    ` ------------------------------------------------
    ` CheckSize values
    `
    ` event list
    Public Event CheckSizeError(vValue)
    ` Properties
    Dim m_CheckSize As Boolean
    Dim m_MinLength As Integer
    Dim m_MaxLength As Integer
    ` Default values
    Const m_def_CheckSize = 0
    Const m_def_MinLength = 0
    Const m_def_MaxLength = 0

Next, update the `InitProperties` event to set the default values for these new properties. See Listing 5.31 for code to add to the `InitProperties` event.

### Listing 5.31. Modifying the InitProperties event.

    `
        ` CheckSize properties
        m_CheckSize = m_def_CheckSize
        m_MinLength = m_def_MinLength
    m_MaxLength = m_def_MaxLength

Now, you need to modify the `ReadProperties` and `WriteProperties` events for the VText control. Listing 5.32 shows you how to modify the `ReadProperties` event.

### Listing 5.32. Modifying the ReadProperties event.

    Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
        `
        ` read design-time property values
        `
        With PropBag
            m_UpperCase = .ReadProperty("UpperCase", m_def_UpperCase)
            m_Digits = .ReadProperty("Digits", m_def_Digits)
            m_Numeric = .ReadProperty("Numeric", m_def_Numeric)
            `
            m_CheckRange = .ReadProperty("CheckRange", m_def_CheckRange)
            m_LowValue = .ReadProperty("LowValue", m_def_LowValue)
            m_HighValue = .ReadProperty("HighValue", m_def_HighValue)
            `
            m_CheckSize = .ReadProperty("CheckSize", m_def_CheckSize)
            m_MinLength = .ReadProperty("MinLength", m_def_MinLength)
            m_MaxLength = .ReadProperty("MaxLength", m_def_MaxLength)
           `
            Text1.Text = .ReadProperty("Text", UserControl.Name)
        End With
        `
    End Sub

Now refer to Listing 5.33 to update the `WriteProperties` event.

### Listing 5.33. Modifying the WriteProperties event.

    Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
        `
        ` save design-time property values
        `
        With PropBag
            Call .WriteProperty("UpperCase", m_UpperCase, m_def_UpperCase)
            Call .WriteProperty("Digits", m_Digits, m_def_Digits)
            Call .WriteProperty("Numeric", m_Numeric, m_def_Numeric)
            `
            Call .WriteProperty("CheckRange", m_CheckRange, m_def_CheckRange)
            Call .WriteProperty("LowValue", m_LowValue, m_def_LowValue)
            Call .WriteProperty("HighValue", m_HighValue, m_def_HighValue)
            `
            Call .WriteProperty("CheckSize", m_CheckSize, m_def_CheckSize)
            Call .WriteProperty("MinLength", m_MinLength, m_def_MinLength)
            Call .WriteProperty("MaxLength", m_MaxLength, m_def_MaxLength)
            `
            Call .WriteProperty("Text", Text1.Text, UserControl.Name)
        End With
        `

`End Sub` Now that you've handled the housekeeping chores for this option, you're ready to code the new properties. Listing 5.34 shows the `Let` and `Get` methods for the three new properties.

### Listing 5.34. Coding the Let and Get methods for the CheckSize properties.

    Public Property Get CheckSize() As Boolean
        CheckSize = m_CheckSize
    End Property

    Public Property Let CheckSize(ByVal vNewValue As Boolean)
        m_CheckSize = vNewValue
        PropertyChanged "CheckSize"
    End Property

    Public Property Get MinLength() As Variant
        MinLength = m_MinLength
    End Property

    Public Property Let MinLength(ByVal vNewValue As Variant)
        `
        If IsNumeric(vNewValue) = False Then
            Error 380 ` invalid property
        Else
            m_MinLength = Val(vNewValue)
            PropertyChanged "MinLength"
        End If
        `
    End Property

    Public Property Get MaxLength() As Variant
        MaxLength = m_MaxLength
    End Property

    Public Property Let MaxLength(ByVal vNewValue As Variant)
        `
        If IsNumeric(vNewValue) = False Then
            Error 380 ` invalid property
        Else
            m_MaxLength = Val(vNewValue)
            PropertyChanged "MaxLength"
        End If
        `
    End Property

Notice, again, the use of `IsNumeric()` and the Error keyword to check for and report invalid property values. After completing the new properties, create a new `Public Sub` called `CheckSizeValidate` and enter the code shown in Listing 5.35.

### Listing 5.35. Coding the CheckSizeValidate method.

    Public Sub CheckSizeValidate()
        `
        ` check for proper size
        `
        Dim varTemp As Variant
        `
        varTemp = Len(Text1.Text)
        `
        If varTemp < m_MinLength Or varTemp > m_MaxLength Then
            RaiseEvent CheckSizeError(varTemp)
        End If
        `
    End Sub

Finally, because this is a form-level routine, modify the `Text1_LostFocus` event to match the code shown in Listing 5.36.

### Listing 5.36. Modifying the Lost_Focus event of the TextBox.

    Private Sub Text1_LostFocus()
        `
        If CheckRange = True Then InRangeValidate
        If CheckSize = True Then CheckSizeValidate
        `
    End Sub

Save (and close) the control project before you switch to the `TEST` project to test the new options.

#### Testing the CheckSize Options of the VText Control

Now select the `TEST.FRM` form and add a label and a VText control. Set the label caption to vtxCheckSize. Set the VText control's name to vtxCheckSize. Then set the vtxCheckSize MinLength property to 5 and the MaxLength property to 10. Set the CheckSize property to True and the UpperCase property to True. Finally, set the Text property to "". Refer to Figure 5.17 for the location and size of the controls.
Figure 5.17. Laying out the vtxCheckSize controls.

After laying out the controls, add the code in Listing 5.37 to the `CheckSizeError` event of the vtxCheckSize control.

### Listing 5.37. Coding the error message for vtxCheckSize.

    Private Sub vtxCheckSize_CheckSizeError(vValue As Variant)
        `
        MsgBox "Invalid Entry Length! [" & CStr(vValue) & "]"
        `
    End Sub

You should also add code to the `cmdOK_Click` event to handle validation if the user never loses focus on the vtxCheckSize control. See Listing 5.38 for modifications to the code.

### Listing 5.38. Modifying the cmdOK_Click event code.

    Private Sub cmdOK_Click()
        `
        If vtxInRange.CheckRange = True Then
            vtxInRange.InRangeValidate
        End If
        `
        If vtxCheckSize.CheckSize = True Then
            vtxCheckSize.CheckSizeValidate
        End If
        `
    End Sub

Now save and run the `TEST` project. You see an error message when you enter a value in the vtxCheckSize field that is less than three characters or more than ten characters long.

#### Adding Required and Conditional Options to the VText Control

You can also add code to mark the data entry field as required or to set conditional requirements on fields. This was illustrated in the first part of the chapter. Listing 5.39 shows the complete general declarations section with the modifications to include the values for the required and conditional options.

### Listing 5.39. Modifying general declarations for the Required option.

    Option Explicit

    ` ------------------------------------------------
    ` KeyFilter values
    `
    ` Event list
    Public Event NumericError(KeyAscii)
    Public Event DigitsError(KeyAscii)
    `Default Property Values:
    Const m_def_UpperCase = 0
    Const m_def_Digits = 0
    Const m_def_Numeric = 0
    `Property Variables:
    Dim m_UpperCase As Boolean
    Dim m_Digits As Boolean
    Dim m_Numeric As Boolean
    Dim m_Text As String

    ` --------------------------------------------------
    ` CheckRange values
    `
    ` event list
    Public Event InRangeError(vValue)
    ` Properties
    Dim m_CheckRange As Boolean
    Dim m_HighValue As Variant
    Dim m_LowValue As Variant
    ` Default Values
    Const m_def_CheckRange = 0
    Const m_def_LowValue = 0
    Const m_def_HighValue = 0

    ` ------------------------------------------------
    ` CheckSize values
    `
    ` event list
    Public Event CheckSizeError(vValue)
    ` Properties
    Dim m_CheckSize As Boolean
    Dim m_MinLength As Integer
    Dim m_MaxLength As Integer
    ` Default values
    Const m_def_CheckSize = 0
    Const m_def_MinLength = 0
    Const m_def_MaxLength = 0

    ` ------------------------------------------------
    ` Required values
    `
    Public Event RequiredError() ` event
    Dim m_Required As Boolean ` property
    Const m_def_Required = 0 ` default

    ` ----------------------------------------------
    ` Conditional values
    Public Event ConditionalError(strMsg) ` event
    Dim m_CheckConditional As Boolean ` property
    Const m_def_CheckConditional = 0 ` default

Next comes the `InitProperties` event. Listing 5.40 shows the entire `InitProperties` event, including the Required and Conditional values.

### Listing 5.40. The final UserControl_InitProperties event code.

    Private Sub UserControl_InitProperties()
        `
        ` initialize control values
        `
        With UserControl
            .Width = 1200
            .Height = 300
        End With
        `
        Text1.Text = UserControl.Name
        `
        ` KeyFilter properties
        m_UpperCase = m_def_UpperCase
        m_Digits = m_def_Digits
        m_Numeric = m_def_Numeric
        `
        ` InRange properties
        m_CheckRange = m_def_CheckRange
        m_HighValue = m_def_HighValue
        m_LowValue = m_def_LowValue
        `
        ` CheckSize properties
        m_CheckSize = m_def_CheckSize
        m_MinLength = m_def_MinLength
        m_MaxLength = m_def_MaxLength
        `
        ` Required property
        m_Required = m_def_Required
        `
        ` Conditional property
        m_CheckConditional = m_def_CheckConditional
        `
    End Sub

After updating the `InitProperties` event, you can add the actual properties Required (Boolean) and CheckConditional (Boolean). Listing 5.41 has the code for the `Let` and `Get` methods for these two properties.

### Listing 5.41. Coding the Let and Get methods for the new properties.

    Public Property Get Required() As Boolean
        Required = m_Required
    End Property

    Public Property Let Required(ByVal vNewValue As Boolean)
        m_Required = vNewValue
        PropertyChanged "Required"
    End Property

    Public Property Get CheckConditional() As Boolean
        CheckConditional = m_CheckConditional
    End Property

    Public Property Let CheckConditional(ByVal vNewValue As Boolean)
        m_CheckConditional = vNewValue
        PropertyChanged "CheckConditional"
    End Property

After you've coded the property routines, you can update the `WriteProperties` event. Listing 5.42 shows the complete code for this event.

### Listing 5.42. Complete code for the WriteProperties event of the UserControl.

    Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
        `
        ` save design-time property values
        `
        With PropBag
            Call .WriteProperty("UpperCase", m_UpperCase, m_def_UpperCase)
            Call .WriteProperty("Digits", m_Digits, m_def_Digits)
            Call .WriteProperty("Numeric", m_Numeric, m_def_Numeric)
            `
            Call .WriteProperty("CheckRange", m_CheckRange, m_def_CheckRange)
            Call .WriteProperty("LowValue", m_LowValue, m_def_LowValue)
            Call .WriteProperty("HighValue", m_HighValue, m_def_HighValue)
            `
            Call .WriteProperty("CheckSize", m_CheckSize, m_def_CheckSize)
            Call .WriteProperty("MinLength", m_MinLength, m_def_MinLength)
            Call .WriteProperty("MaxLength", m_MaxLength, m_def_MaxLength)
            `
            Call .WriteProperty("Required", m_Required, m_def_Required)
            `
            Call .WriteProperty("CheckConditional", m_CheckConditional, m_def_CheckConditional)
            `
            Call .WriteProperty("Text", Text1.Text, UserControl.Name)
        End With
        `
    End Sub

Listing 5.43 shows the complete code for the `ReadProperties` event. Match this to the one in your project.

### Listing 5.43. Final version of the ReadProperties event.

    Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
        `
        ` read design-time property values
        `
        With PropBag
            m_UpperCase = .ReadProperty("UpperCase", m_def_UpperCase)
            m_Digits = .ReadProperty("Digits", m_def_Digits)
            m_Numeric = .ReadProperty("Numeric", m_def_Numeric)
            `
            m_CheckRange = .ReadProperty("CheckRange", m_def_CheckRange)
            m_LowValue = .ReadProperty("LowValue", m_def_LowValue)
            m_HighValue = .ReadProperty("HighValue", m_def_HighValue)
            `
            m_CheckSize = .ReadProperty("CheckSize", m_def_CheckSize)
            m_MinLength = .ReadProperty("MinLength", m_def_MinLength)
            m_MaxLength = .ReadProperty("MaxLength", m_def_MaxLength)
            `
            m_Required = .ReadProperty("Required", m_def_Required)
            `
            m_CheckConditional = .ReadProperty("CheckConditional", m_def_CheckConditional)
            `
            Text1.Text = .ReadProperty("Text", UserControl.Name)
        End With
        `
    End Sub

After adding the property methods, you need to add the `CheckRequired` method. Use the Tools | Add Procedure menu option to add a `Public Sub` called `CheckRequired` and enter the code from Listing 5.44.

### Listing 5.44. Coding the CheckRequired method.

    Public Sub CheckRequired()
        `
        If m_Required = True And Len(Trim(Text1.Text)) = 0 Then
            RaiseEvent RequiredError
        End If
        `
    End Sub

Then you can add the `Public Sub ConditionalValidate` routine and copy the code from Listing 5.45 to your project.

### Listing 5.45. Coding the ConditionalValidate method.

    Public Sub ConditionalValidate(MasterName As String, ChildName As String, ChildValue As Variant)
        `
        ` make sure conditional field is also filled in
        `
        Dim strMsg As String
        `
        strMsg = "The [" & MasterName & "] field contains data, " & _
           " the [" & ChildName & "] field must also contain valid data"
        `
        If Len(Trim(ChildValue)) = 0 Then
            RaiseEvent ConditionalError(strMsg)
        End If
        `
    End Sub

This last routine is unique in that it is the only routine that requires parameters to be passed. Because `ConditionalValidate` is checking the status of more than one control, you need to supply it with the name and contents of the dependent control.

Finally, update the `Text1_LostFocus` event to match the one shown in Listing 5.46.

### Listing 5.46. Updated Text1_LostFocus event.

    Private Sub Text1_LostFocus()
        `
        If CheckRange = True Then InRangeValidate
        If CheckSize = True Then CheckSizeValidate
        If Required = True Then CheckRequired
        `
    End Sub

That's the end of the coding for the VText control. Save this control for now while you test one more time.

#### Testing the Required and Conditional Options of the VText Control

Bring up the TEST.FRM form and add two more VText controls to the form. Set the Name property of one to vtxRequired and of the other to vtxConditional. Add two labels with their captions set to vtxRequired and vtxConditional. For the vtxRequired control, set the CheckRequired property to true. For the vtxConditional control, set CheckConditional to true. Refer to Figure 5.18 for layout details.
Figure 5.18. Laying out the Required and Conditional options form.

Now add the code shown in Listing 5.47 to each of the error events and the `cmdOK_Click` events.

### Listing 5.47. Adding code for the Required and Conditional options.

    Private Sub cmdOK_Click()
        `
        If vtxInRange.CheckRange = True Then
            vtxInRange.InRangeValidate
        End If
        `
        If vtxCheckSize.CheckSize = True Then
            vtxCheckSize.CheckSizeValidate
        End If
        `
        If vtxConditional.CheckConditional = True Then
            vtxConditional.ConditionalValidate _
              "vtxConditional", "vtxUpper", vtxUpper.Text
        End If
        `
    End Sub

    Private Sub vtxConditional_ConditionalError(strMsg As Variant)
        `
        MsgBox strMsg
        `
    End Sub

    Private Sub vtxRequired_RequiredError()
        `
        MsgBox " This is a required field!"
        `
    End Sub

Notice the use of parameters when calling the `ConditionalValidate` method. You should also note that the `ConditionalValidate` method is the only method in this control that must be executed by the user. There is no normal event that forces a conditional input check across multiple data entry fields on a form. Now save and run the project. You should see error messages when you attempt to violate the data entry rules on these controls.

#### Compiling and Registering the VText Control

Now that you have coded, saved, and tested the VText control, you are ready to create an ActiveX control (or OCX) from the file. Select File | Make OCX from the main menu. You are prompted to confirm the OCX name (keep `CUSTOMVALIDATION.OCX`) and, after you click OK, Visual Basic 5 creates a complete ActiveX control for your use. You use this control in the next, and final, section when you build the CompanyMaster form.

After creating the OCX, save the project group. This might also be a good time to take a short break before you move on to the last section of the chapter.

### Building the CompanyMaster Input Form

Because you learned about data controls and form design in Day 4, "Creating Data Entry Forms with Bound Controls," and developed an ActiveX control to handle input validation in the first part of today's lesson, you are ready to design the first data entry form for the CompanyMaster data table.

Following are the four basic steps to coding data entry forms in Visual Basic 5:

- Define the basic form.

- Place input controls and prompts.

- Add and code command buttons.

- Code input validation.

You can use these steps in coding any forms for Visual Basic 5. You follow these steps while you construct the CompanyMaster data entry form.

#### Defining the Basic Form

The first step is to set the size of the data entry form, add the input palette, and add any frames needed. These are the basic components of the form. All other controls are placed upon the palette within the frames you install in this step.

> TIP: If you put the palette and frames up first, you can place all other controls as so-called children of the palette and frames. This way, when you move the frame, all controls within that frame also move. The same thing happens when you move the large palette. Creating forms this way makes it easy to make slight adjustments later.

At this time, you also add the data control and the final exit button to the form. Use the information in Table 5.1 and Figure 5.19 as a guide for sizing and placing the basic form components.
Figure 5.19. Laying out the basic form components of the CompanyMaster form.

Save your form as `MASTER.FRM`, and save the project as `MASTER.VBP`.
Table 5.1. CompanyMaster form components.

Object | Property | Setting
Form | Name | frmMaster
| Border Style | 1--Fixed Single
| Caption | Company Master
| Height | 5955
| Left | 195
| Max Button | False
| Top | 330
| Width | 9105
| Save Filename | Mast01.FRM
SSPanel | Caption | "" (blank)
| Height | 4815
| Left | 120
| Top | 120
| Width | 8715
Data Control | BackColor | Light Gray
| Caption | Company Master
| DatabaseName | `C:\TYSDBVB5\DATA\MASTER.MDB`
| Height | 330
| Left | 300
| RecordSource | Company Master
| Top | 4500
| Width | 3615
Command Button | Name | cmdExit
| Caption | E&xit
| Height | 330
| Left | 7500
| Top | 5100
| Width | 1200
SSFrame | Caption | Company
| Height | 2355
| Left | 120
| Top | 180
| Width | 7155
SSFrame | Caption | Contact
| Height | 1635
| Left | 120
| Top | 2640
| Width | 3675
SSFrame | Caption | Other
| Height | 1635
| Left | 3900
| Top | 2640
| Width | 3375

####
Placement of Input Controls and Prompts

Now you are ready to place the input controls on the form. Each input control has an associated screen prompt. All screen prompts are done using the Visual Basic 5 Label control. You use VTextBox, MaskedEdit, and Check3D controls for input fields. You also use SSPanel3D controls for the read-only display fields.

> NOTE: Do not double-click controls onto the form. Always single-click the control icon in the Tools window, and then use the mouse to paint the control within the proper frame control. This ensures that the controls are children of the frame control and move whenever you move the frame. To play it safe, always select the panel by clicking it prior to selecting a control to place on it.

Because you are using the Win95 design specifications, you need to spend time aligning and sizing controls accordingly. All the information you need to properly size and place the controls is contained in Table 5.2 and Figure 5.20.
Figure 5.20. Laying out the input controls and prompts.
Table 5.2. CompanyMaster input controls and prompts.

Object | Property | Setting
VTextBox | Name | vxtCompanyName
| DataField | CompanyName
| DataSource | Data1
| Height | 330
| Left | 1380
| Top | 240
| Width | 2100
VTextBox | Name | vxtAddr1
| DataField | Addr1
| DataSource | Data1
| Height | 330
| Left | 1380
| Top | 660
| Width | 2100
VTextBox | Name | vxtAddr2
| DataField | Addr2
| DataSource | Data1
| Height | 330
| Left | 1380
| Top | 1080
VTextBox | Name | vxtCity
| DataField | City
| DataSource | Data1
| Height | 330
| Left | 1380
| Top | 1500
| Width | 2100
VTextBox | Name | vxtCountry
| DataField | Country
| DataSource | Data1
| Height | 330
| Left | 1380
| Top | 1920
| Width | 2100
SSPanel3D | Name | pnlEntryNbr
| Alignment | 4--Right Just Middle
| BevelOuter | 1--Inset
| BorderWidth | 1
| DataField | EntryNbr
| DataSource | Data1
| FontBold | False
| Height | 330
| Left | 5160
| Top | 240
| Width | 1800
SSPanel3D | Name | pnlLastUpdated
| Alignment | 4--Right Just Middle
| BevelOuter | 1--Inset
| BorderWidth | 1
| DataField | LastUpdated
| DataSource | Data1
| Height | 330
| Left | 5160
| Top | 660
| Width | 1800
SSCheck3D | Name | chkCustFlag
| Alignment | 1--Right Justify
| Caption | Customer Flag:
| DataField | CustFlag
| DataSource | Data1
| Height | 330
| Left | 3900
| Top | 1080
| Width | 1455
VTextBox | Name | vxtStateProv
| DataField | StateProv
| DataSource | Data1
| Height | 330
| Left | 5160
| Top | 1500
| Width | 1800
MaskEdBox | Name | mskPostCode
| DataField | PostalCode
| DataSource | Data1
| Height | 330
| Left | 5160
| Mask | #####-####
| PromptInclude | False
| Top | 1920
| Width | 1800
VTextBox | Name | vxtLastName
| DataField | LastName
| DataSource | Data1
| Height | 330
| Left | 1380
| Top | 240
| Width | 2100
VTextBox | Name | vxtFirstName
| DataField | FirstName
| DataSource | Data1
| Height | 330
| Left | 1380
| Top | 660
| Width | 2100
VTextBox | Name | vxtTitle
| DataField | Title
| DataSource | Data1
| Height | 330
| Left | 1380
| Top | 1080
| Width | 2100
MaskEdBox | Name | mskVoicePhone
| DataField | VoicePhone
| DataSource | Data1
| Height | 330
| Left | 1380
| Mask | (###) ###-####
| PromptInclude | False
| Top | 240
| Width | 1800
MaskEdBox | Name | mskExtension
| DataField | Extension
| DataSource | Data1
| Height | 330
| Left | 1380
| Mask | ####
| PromptInclude | False
| Top | 660
| Width | 1800
MaskEdBox | Name | mskFAXPhone
| DataField | FAXPhone
| DataSource | Data1
| Height | 330
| Left | 1380
| Mask | (###) ###-####
| PromptInclude | False
| Top | 1080
| Width | 1800
Label | Caption | Company Name:
| BackStyle | 0--Transparent
| Height | 330
| Left | 120
| Top | 240
| Width | 1200
Label | Caption | Address Line1:
| BackStyle | 0--Transparent
| Height | 330
| Left | 120
| Top | 660
| Width | 1200
Label | Caption | Address Line2:
| BackStyle | 0--Transparent
| Height | 330
| Left | 120
| Top | 1080
| Width | 1200
Label | Caption | City:
| BackStyle | 0--Transparent
| Height | 330
| Left | 120
| Top | 1500
| Width | 1200
Label | Caption | Country:
| BackStyle | 0--Transparent
| Height | 330
| Left | 120
| Top | 1920
| Width | 1200
Label | Caption | Entry Number:
| BackStyle | 0--Transparent
| Height | 330
| Left | 3900
| Top | 240
| Width | 1200
Label | Caption | Last Updated:
| BackStyle | 0--Transparent
| Height | 330
| Left | 3900
| Top | 660
| Width | 1200
Label | Caption | State/Prov:
| BackStyle | 0--Transparent
| Height | 330
| Left | 3900
| Top | 1500
| Width | 1200
Label | Caption | Postal Code:
| BackStyle | 0--Transparent
| Height | 330
| Left | 3900
| Top | 1920
| Width | 1200
Label | Caption | Last Name:
| BackStyle | 0--Transparent
| Height | 330
| Left | 120
| Top | 240
| Width | 1200
Label | Caption | First Name:
| BackStyle | 0--Transparent
| Height | 330
| Left | 120
| Top | 660
| Width | 1200
Label | Caption | Title:
| BackStyle | 0--Transparent
| Height | 330
| Left | 120
| Top | 1080
| Width | 1200
Label | Caption | Voice Phone:
| BackStyle | 0--Transparent
| Height | 330
| Left | 120
| Top | 240
Label | Caption | Extension:
| BackStyle | 0--Transparent
| Height | 330
| Left | 120
| Top | 660
| Width | 1200
Label | Caption | FAX Phone:
| BackStyle | 0--Transparent
| Height | 330
| Left | 120
| Top | 1080
| Width | 1200

> NOTE: Please note that we have used the U.S. nine-digit ZIP code in this exercise. You might want to modify this mask if you live in a country that has a different ZIP code format.

You need to add one more set of input controls to the form--the Company Logo controls. Refer to Table 5.3 and Figure 5.21 for sizing and placement of the Image control that holds the picture and the associated label control for the prompt. You add code behind the image control in the next section.
Figure 5.21. Adding the Company Logo controls.
Table 5.3. CompanyMaster Company Logo controls.

Object | Property | Setting
Image | BorderStyle | 1--Fixed Single
| DataField | CompanyLogo
| DataSource | Data1
| Height | 1200
| Left | 7380
| Stretch | -1 True
| Top | 360
| Width | 1200
Label | Caption | Company Logo:
| BackStyle | 0--Transparent
| Height | 330
| Left | 7380
| Top | 120
| Width | 1200

####
Adding and Coding Command Buttons

Next, add the command buttons. Although you already have the Visual Basic 4 data control on the form, you need additional buttons to allow the user to perform adds, deletes, updates, finds, and so on. You also add a button to pop up a small form for adding comments to the data record. Refer to Table 5.4 and Figure 5.22 for sizing and placement information.
Table 5.4. CompanyMaster command buttons.

Object | Property | Setting
CommandButton | Name | cmdAdd
| Caption | &Add
| Height | 330
| Left | 7380
| Top | 1620
| Width | 1200
CommandButton | Name | cmdUpdate
| Caption | &Update
| Height | 330
| Left | 7380
| Top | 2040
| Width | 1200
CommandButton | Name | cmdRestore
| Caption | &Restore
| Height | 330
| Left | 7380
| Top | 2880
| Width | 1200
CommandButton | Name | cmdDelete
| Caption | &Delete
| Height | 330
| Left | 7380
| Top | 3300
| Width | 1200
CommandButton | Name | cmdFind
| Caption | &Find
| Height | 330
| Left | 7380
| Top | 3720
| Width | 1200
CommandButton | Name | cmdNotes
| Caption | &Notes
| Height | 330
| Left | 7380
| Top | 4140
| Width | 1200

######

Figure 5.22. Adding the CompanyMaster command buttons.

First, add the following line of code in the general declaration section of the form.

    Dim nAddRec as Integer

The following code sections should be placed behind each button. You placed identical code behind other examples earlier this week. Begin with Listing 5.48, which shows the code to enter behind the cmdAdd command button.

### Listing 5.48. Adding data records.

    Private Sub cmdAdd_Click()
        `
        Data1.Recordset.AddNew ` add a new record to table
        nAddRec = True
        `
    End Sub

Now add the code in Listing 5.49 to the cmdExit button. This code unloads the form when the Exit button is selected.

### Listing 5.49. Unloading the CompanyMaster form.

    Sub cmdExit_Click ()
      Unload Me ` close myself (better than END)
    End Sub

Now enter the code in Listing 5.50 to the `cmdFind_Click` event. When executed, this code queries the user to enter an appropriate search string.

### Listing 5.50. Finding data records.

    Private Sub cmdFind_Click()
        `
        Dim nResult As Integer
        Dim cFind As String
        Dim cBookMark As String
        `
        cFind = InputBox("Enter Search String:", "CompanyMaster FIND")
        If Len(cFind) > 0 Then
           cBookMark = Data1.Recordset.Bookmark
           Data1.Recordset.FindFirst cFind
           If Data1.Recordset.NoMatch Then
              MsgBox "Can't Find [" & cFind & "]", vbExclamation, "Find Error"
              Data1.Recordset.Bookmark = cBookMark
           End If
        End If
        `
    End Sub

The code in Listing 5.51 should be entered into the `cmdRestore_Click` event to restore the controls to their original value when the cmdRestore command button is selected.

### Listing 5.51. Restoring the data controls.

    Private Sub cmdRestore_Click()
        `
        nAddRec = False
        Data1.UpdateControls ` restore controls from table
        `
    End Sub

Now enter code to save the data. Use Listing 5.52 as a guide and enter this code into the `cmdUpdate_Click` event.

### Listing 5.52. Writing a record.

    Private Sub cmdUpdate_Click()
        `
        If nAddRec = False Then
           Data1.Recordset.Edit
        End If
        `
        Data1.Recordset.Update ` write record to table
        `
        nAddRec = False
        `
    End Sub

Listing 5.53 contains the code that should now be entered into the `cmdDelete_Click` event. This code deletes the displayed record after the user confirms the deletion.

### Listing 5.53. Deleting a record.

    Private Sub cmdDelete_Click()
        `
        Dim nResult As Integer
        `
        ` give user chance to reconsider
        nResult = MsgBox("Are you sure?", 1, "Delete Record")
        If nResult = 1 Then
           Data1.Recordset.Delete
           Data1.Recordset.MoveFirst
        End If
        `
    End Sub

You need to add code behind the Image control to allow users to update the CompanyLogo field. Users should be able to locate a file on the disk, and then save it to the field. The form then displays the saved image. You can give users access to loading files by adding the Visual Basic 5 CommonDialog control to the form. Select the CommonDialog control from the Tools window and place it at the bottom of the form. It does not really matter where it is placed--the CommonDialog control is invisible at runtime. Once the control is on the form, add the code in Listing 5.54 to the `Image1_DblClick` event.

### Listing 5.54. Updating the company logo.

    Private Sub Image1_DblClick()
        `
        ` set dialog properties
        CMDialog1.Filter = "Bitmap (*.bmp)|*.bmp|Metafiles (*.wmf)|*.wmf|"
        CMDialog1.DialogTitle = "Load Company Logo"
        `
        ` run dialog box
        CMDialog1.Action = 1
        `
        ` if they picked a file, load it up
        On Error GoTo PicErr ` in case user picks a bad file
        If Len(CMDialog1.filename) <> 0 Then
           Image1.Picture = LoadPicture(CMDialog1.filename)
        End If
        `
        ` all done, go to exit
        GoTo PicExit
        `
        ` handle bad picture error
    PicErr:
        MsgBox "Unable to load selected file.", vbExclamation, "Picture Error"
        Resume Next
        `
        ` final exit of procedure
    PicExit:
        `
    End Sub

The code in Listing 5.54 sets file type and caption properties of the common dialog box, runs the dialog, and then, if a file has been selected, attempts to save it to the image control. You add a little error trapping here in case the user selects an invalid file type.

#### Adding Input Validation

The last step in creating Visual Basic 5 data entry forms is adding the input validation routines. The following is a list of the input rules you should use when coding the validation routines:

- The following fields are required for each form:
  - CompanyName

  - Addr1

  - City

  - State/Province

- The following dependent field rules apply to the form:
  - If the Addr2 field has data, the Addr1 field must have data.

  - If the FirstName field has data, the LastName field must have data.

- The StateProv field should allow only uppercase data entry.

First, set the UpperCase property of the vxtStateProv control to True. This forces all data input into this control to upper case. You can accomplish most of these validation checks using the Required property of the new VText control. However, you also need to call the `conditionalValidate` method for the three controls that require conditional input.

To start, set the Required property to True for the following controls:

- CompanyName

- Addr1

- City

- State/Province

Next, you need to add message code in the error event of the required fields. Listing 5.55 shows the code to place in the various events.

### Listing 5.55. Coding the error messages for the required fields.

    Private Sub vxtAddr1_RequiredError()
        `
        MsgBox "This is a required field", vbExclamation, vxtAddr1.DataField
        `
    End Sub

    Private Sub vxtCity_RequiredError()
        `
        MsgBox "This is a required field", vbExclamation, vxtCity.DataField
        `
    End Sub

    Private Sub vxtCompanyName_RequiredError()
        `
        MsgBox "This is a required field", vbExclamation, vxtCompanyName.DataField
        `
    End Sub

    Private Sub vxtStateProv_RequiredError()
        `
        MsgBox "This is a required field", vbExclamation, vxtStateProv.DataField
        `
    End Sub

Now you need to add the code that checks for conditional input. The best place to put the conditional code is in a single `ValidateForm` method. This covers all the field- and form-level validation in one pass. Listing 5.56 shows the `ValidateForm` method for this form. Create a new Sub called `ValidateForm` and enter the code from listing 5.56.

### Listing 5.56. Performing validation checks.

    Private Sub ValidateForm()
        `
        ` check required fields
        vxtCompanyName.CheckRequired
        vxtAddr1.CheckRequired
        vxtCity.CheckRequired
        vxtStateProv.CheckRequired
        `
        ` check conditional fields
        vxtAddr2.ConditionalValidate vxtAddr2.DataField, vxtAddr1.DataField, vxtAddr1.Text
        vxtFirstName.ConditionalValidate vxtFirstName.DataField, vxtLastName.DataField, vxtLastName.Text
        `
    End Sub

After you enter this code, you need to add a few lines to the `Data1_Validate` event. The code in Listing 5.57 calls the validation routine each time the Update button is clicked or the arrow keys are pressed on the data control.

### Listing 5.57. Calling validation routines when the Update button is pressed.

    Private Sub Data1_Validate(Action As Integer, Save As Integer)
        `
        ValidateForm
        `
    End Sub

### Summary

Today you learned how to perform input validation on data entry forms. You learned that input validation tasks can be divided into three areas:

- Key filtering: Preventing unwanted keyboard input

- Field-level validation: Validating input for each field

- Form-level Validation: Validating input across several fields

You also learned that you should ask yourself a few basic questions when you are developing validation rules for your form.

- Is it a required field?

- What characters are valid/invalid for this field? (Numeric input only, capital letters only, no spaces allowed, and so on.)

- For numeric fields, is there a high/low range limit? (Must be greater than zero and less than 1000, can't be less than 100, and so on.)

- Is there a list of valid values for this field? (Can the user enter only Retail, Wholesale, or Other; Name must already be in the Customer table, and so on.)

- Is this a conditional field? (If users enter Yes in field A, then they must enter something in field C.)

You learned how to write keyboard filter validation functions using the Visual Basic 5 `KeyPress` event. You learned how to write field-level validation functions that check for valid input ranges, input that is part of a list of valid data, and input that is within minimum and maximum length requirements. You also learned how to write validation functions that make sure dependent fields have been filled out properly.

Finally, you learned how to use Visual Basic 5 to create your own custom control that incorporates all the validation techniques you learned in this chapter. You can use this ActiveX control in all your future Visual Basic projects.

You also applied your knowledge of bound data controls, Visual Basic 5 data entry form design, and validation processing to create the data entry form for the CompanyMaster data table.

### Quiz

  1. What is the difference between input validation and error trapping?
  2. What value must you subtract from a lowercase character to get its uppercase ASCII value?
  3. What Visual Basic 4 event occurs every time a key is pressed on your keyboard?
  4. Do characters in a validation list need to be entered in any particular order?
  5. What does the following code mean?

>     If Len(Trim(txtUpper)) <> 0 then

  6. Should conditional field validation be performed at the field level or the form level?
  7. When should you load validation lists?
  8. What do the three sections of the format property of the MaskedEdit control represent? What character separates these sections?

### Exercises

  1. Write code to allow entry of only capital letters in a field. The user should be able to enter control codes, but not numbers or symbols.
  2. Write the format property for a MaskedEdit control that rounds the entered number to the nearest hundredth, includes commas in all numbers, and places an en dash (-) in front of negative numbers.
  3. Write a form-level validation routine that requires that entry be made into a field named txtDate before a record can be saved by pressing a button named cmdOK.
  4. Write the code to fill a combo box named cboEmployees with your employees' last names of Smith, Andersen, Jones, and Jackson. What property do you set in the combo box control to sort these names alphabetically?

| Use of this site is subject certain Terms & Conditions. Copyright (c) 1996-1999 EarthWeb, Inc.. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Please read our privacy policy for details.