Initialization and Validation

Initialization and Validation, known as IV (pronounced "Eye-Vee"), are often the toughest parts of a Wizard (not including actually performing the task your Wizard is designed to handle for your user). Putting some thought into the design of these routines can save you both development time and testing headaches.

Both initialization and validation operate at two major levels, control level and page level. Control-level IV occurs when an event from one control initializes/validates another control. This can be as simple as enabling the Next button when text is entered into an edit box. Page-level IV applies to a set of controls and occurs on a page change, rather than within the same page. There is not always a strict separation between the two; frequently, page-level IV is just a superset of control-level IV for all the individual controls on a page plus some setup for the next page.

Initialization Overview

In a single dialog box, you generally initialize all controls before the dialog is displayed and validate all input when the user dismisses the dialog (page-level IV). More sophisticated dialogs may initialize and/or change the contents of some controls based on choices made with other controls (control-level IV). For example, choosing a specific state in one list could cause another list to refresh with related ZIP Codes.

In a Wizard, the same interrelationships can occur between controls on a single page as well as between pages. When you develop a Wizard, you need to answer the following questions:

Here are some options to choose from:

The simple choice is to do all initialization before any page displays and page-level validation as the user moves to the next page. In practice, this is nearly impossible because you are often handling many interdependencies.

In reality, most Wizards need combinations of all these choices. Before the Wizard starts, you will do some initialization. Between each page, you update some controls based on the user selection in a previous page. Between pages, you check for the beginning or end of your Wizard so you can enable/disable the Back button and the Finish button as needed. Both moving forward and moving backward require you to update the dialog caption. Proper Wizard initialization can become complicated without a well-defined system for initializing based upon specific points in Wizard program execution.

Page-Level Initialization

The initialization routine below is a master routine that combines all the page-level controls needed for most Wizards. While it is written in the context of the example found on the CD, it is designed to cover the most common issues you'll need in your Wizards. It is called before the Wizard displays for the first time and also between each page, when the Next button is selected.

''''''''''''''''''''''''''''''''''''''''''''''''''''''''
''' Subroutine: InitWizard
''' Arguments:  iInitPage - Integer: Page being initialized
''' (-1 is special case: First time dialog displayed)
''' Comments:   Initializes all pages of the wizard
'''    Contains 4 Initialize sections:
'''    SECTION 1: Before initial dialog display
'''        (iInitPage = -1)
'''    SECTION 2: Before any page is displayed
'''        EXCEPT the first time

'''    SECTION 3: Common code on any page display,
'''        no exceptions
'''    SECTION 4: Page specific code
''' Date          Developer           Action
''' --------------------------------------------------
Public Sub InitWizard(iInitPage As Integer = -1)
    ''' SECTION 1: Before initial dialog display
    If iInitPage = -1 Then
        ''' Set the module-level step variable,
        ''' set first page: MultiPage control.
        miCurrentStep = 0
        mpgWizardControl.Value = miCurrentStep
        cmdBack.Enabled = False
        cmdNext.Enabled = False
        cmdFinish.Enabled = False
    ''' SECTION 2: Before any page EXCEPT during
    ''' initial display
    Else
        If miCurrentStep = miMAX_PAGE_INDEX Then
            ''' final page
            cmdFinish.Enabled = True
            cmdNext.Enabled = False
        Else
            cmdFinish.Enabled = False
        End If
        If miCurrentStep > 0 Then
            ''' not first page
            cmdBack.Enabled = True
        Else
            cmdBack.Enabled = False
        End If
    End If
    ''' SECTION 3: Common code for all displays
    ''' Set dialog caption
    Me.Caption = mszBASE_DIALOG_CAPTION & miCurrentStep + 1 _
        & " of " & miMAX_PAGE_INDEX + 1
    ''' SECTION 4: Code for page specific initialization
    Select Case iInitPage    ''' if -1 (first time), handled
                            ''' as special case above
        Case 0        ''' Page 1
            If txtCellEntry.Text = "" Then
                cmdNext.Enabled = False
            Else
                cmdNext.Enabled = True
            End If
        Case 1        ''' Page 2
            If refEntryRange.Text = "" Then
                cmdNext.Enabled = False
            Else
                cmdNext.Enabled = True

            End If
                refEntryRange.SetFocus
        Case 2
            ''' Page 3 (none in this example)
    End Select
End Sub

The routine is designed to group all your page initialization together in one routine. It expects to be called before the Wizard is displayed (passing a " – 1" argument) and as you move between pages.

As you develop your own Wizard, you can copy this code and keep the sections while removing the example-specific code. Of course, you will need to adjust the code to reflect the number of pages in your Wizard, but the basic structure can remain unchanged.

Validation Overview

Validation verifies that the input provided by the user is appropriate for your procedure. If you are expecting the user to select a single cell and multiple cells are selected, you should alert the user to the oversight and require them to correct this error before moving on to the next page. Validation can become complicated, but it's critically important, because bad input can cause problems when your Wizard attempts to complete its procedure. Let's look at the same types of questions we discussed in the initialization sections.

When should you consider running your validation code?

As is the case with initialization, validation doesn't come in a "one size fits all" for Wizards. Your Wizard may require combinations of these validation techniques. A basic strategy is laid out in the example Wizard (detailed below). Generally, you do the validation as soon as is practical. In most cases, this is when the user moves to the next page. If the validation fails, immediately notify the user and continue on the same page rather than moving forward. Ideally, you'll also set the focus to the control at which the first validation failed (if there is more than one control to be validated).

Validation exists in the same two flavors as initialization code: control level and page level. Control-level validation attempts to eliminate gross errors and is attached to specific controls using events. It is often much less strict than page-level validation. Page-level validation is run when the user attempts to move to the next page, and it is generally much stricter. Let's take a look at both types in order to compare and contrast the two levels.

924959049Control-Level Validation Code924959049BCBC

This is the control-level validation code used in our example:

''' Subroutine:    refEntryRange_Change
''' Comments:    Enables the Next button if the box contains
'''                text (the bValidate routine validates the
'''                range.
''' Date          Developer           Action
''' ----------------------------------------------------
'''
Private Sub refEntryRange_Change()
    If refEntryRange.Text = "" Then
        cmdNext.Enabled = False
    Else
        cmdNext.Enabled = True
    End If
End Sub

This simple code is called by the Change event of the text box. It enables or disables the Next button as the user types into the text box. If the text box contains any text, it enables the Next button. This is very loose validation, since it doesn't check to be sure that the text is valid or makes sense within the context of your Wizard. It does prevent users from blatantly skipping required input. In this specific case, we are really looking for a range, not just any text. The change event fires on every character entered into the text box, and it's not practical to validate a range until the user has completed the input. This type of validation is usually done at the page level. When the user attempts to move to the next page, the stricter page-level validation code is run.

924959050Page924959050SFSF-Level Validation Code

The page-level validation routine shares many concepts with the initialization routines. It's divided into logical sections so that it can be adapted to a wide range of Wizards. If you look at the validation code for Page 3, you'll see the stricter page-level validation being applied to the control that was used in our preceding control-level validation example. In this specific case, it is checking to see if the user happens to be in R1C1 mode and makes the appropriate conversion before testing the range. The control-level validation simply looked for any text in the control, and the page-level validated the text as a valid range. The two validations working together create a stronger validation mechanism than either one alone.

Here's the complete listing for the bValidate function:

''' Function:    bValidate
''' Comments:    Used to validate a single page or all pages
'''                of the Wizard. In WizDemo the -1 flag (all
'''                pages) is NOT used, but would be if you were
'''                validating all pages when the finish button
'''                is chosen. There are 2 major sections:
'''                SECTION 1: Code for All Pages Only
'''                SECTION 2: Code for each page of your Wizard.
''' Arguments:    iValidatePage - validate the page passed
'''                (0 based index) If nothing is passed, default
'''                is: validate all pages (-1)
''' Returns:        True if the page validates
''' Date          Developer           Action
''' --------------------------------------------------
'''
Private Function bValidate(Optional iValidatePage As _
    Integer = -1) As Boolean
    Dim bIsAllPages As Boolean  ''' true if -1 is passed
    Dim szTrash As String   ''' used to hold temp values
    ''' Set function to True. If any validation doesn't pass
    ''' it will be changed to False.
    bValidate = True
    ''' set IsAll flag if -1 is passed.
    bIsAllPages = iValidatePage = -1

    ''' SECTION 1: All pages additional code
    If bIsAllPages Then
    ''' placeholder for additional coded needed if dialog
    ''' is being validated as a batch process when Finish
    ''' button is pressed.
    End If
    ''' SECTION 2: Page specific
    ''' if page 1 or all pages (-1)
    If iValidatePage = 0 Or bIsAllPages Then
        If Len(txtCellEntry.Text) < 3 Then
            MsgBox mszNAME_TOO_SHORT, vbOKOnly + _
                vbExclamation, mszERROR_TITLE
            txtCellEntry.SetFocus
            bValidate = False
        End If
    End If
    ''' page 2 or all pages
    If iValidatePage = 1 Or bIsAllPages Then
    ''' Turn off error handling while testing the range.
        On Error Resume Next
        With Application

            If .ReferenceStyle = xlR1C1 Then   ''' convert
                szTrash = .ConvertFormula(refEntryRange, _
                    xlR1C1, xlA1)
                ''' next statement will throw error if
                ''' selection isn't a valid range
                szTrash = .Range(szTrash).Address
            Else
            ''' next statement will throw error if 
            ''' selection isn't a valid range
                szTrash = .Range(refEntryRange).Address
            End If
        End With
        If Err <> 0 Then
            ''' will only happen if range is not valid
            MsgBox mszBAD_SELECTION, vbOKOnly + _
                vbExclamation, mszERROR_TITLE
            refEntryRange.SetFocus
            bValidate = False
        End If
    On Error GoTo 0 ''' reinstate standard error handling
        ''' In a production app,
        ''' reinstate custom error handler
    End If
    ''' if page 3 or all pages (-1)
    If iValidatePage = 2 Or bIsAllPages Then
        ''' Page 3 validation goes here... 
        ''' no validation needed in WizDemo
    End If
End Function

This routine is much easier to follow when viewed in the Microsoft Excel 97 VBE, because the comments stand out and the individual sections are much clearer.

If you're an experienced spreadsheet developer, you may have noticed that the validation doesn't check the worksheet range (from Step 2) to guarantee that the range is unprotected. Because the demo adds a new worksheet to the workbook and the demo workbook is unprotected, this is a reasonable assumption for example code. In production, the Wizard would require an error handler to trap for the user moving to another workbook and selecting a locked range. This is an intentional omission for this demo, but because the structure is strong, adding additional tests would be very simple.