Database Design 3 Hands-on Excercises Hands-on 3: Error handling You have already learned how to deal with compile-time errors. These are errors that violate the rules of VBA syntax. By compiling your code before you run it, these will not cause errors at run-time. Even when your code is correct, run-time errors can occur when the user does something unexpected or something that you had not planned for. In the first part of this exercise you will write code to disable the previous and next buttons when the form is in the first or last record of a form’s recordset. This prevents the user from making this error. However, it is not always possible to prevent errors in this way so. we need to anticipate the types of error the user is likely to make and write our own error-handling routines to deal with them. You will learn how to do this in the second part of the exercise. Add VBA driven navigation to a form You will first replace the default record navigation on the Supplier Details form with custom, VBA-driven navigation buttons created using the Wizard. Open frmSupplierDetails in design view In the toolbox, make sure that the Wizard is ON. Wizard on Command button tool Fig. 1 Create the next record button Fig. 2 533574482 Page 1 of 10 Database Design 3 Hands-on Excercises In the Command Button Wizard select Record Navigation as the category and Go To Next Record as the Action. Click Next and continue through the Wizard to create your button. In the last step, make sure you give the button a meaningful name, such as cmdNext (note use of using the cmd prefix). Click the Code button on the toolbar to open the Visual Basic editor to see the code the Wizard has created for your button. Fig. 3 Open the properties for the button. Note that the Wizard has attached this event procedure to the On Click event of the button. If you click the ellipsis [...] it will take you to the associated code. Create the other buttons for the navigation system in the same way. What buttons will be required? How can you add a button to create a new record? When you have created all the buttons, remove the record navigation from the bottom of the form. 533574482 Page 2 of 10 Database Design 3 Hands-on Excercises Test your buttons. What happens when you click the next record button when you are already in the last record? (The last record in the recordset is the blank record) You get the error message generated by the error code in the event procedure. You can’t go to the next record because it doesn’t exist! You will get the same message if you click the previous button when you are in the first record. Smart navigation buttons You will now write code to disable these buttons to prevent the user from clicking them when the associated action is not possible. Open the supplier details form in design view. Next record button We will first deal with the next record button. We want to grey it out when the form is displaying a blank new record which is the last record in the recordset. We need to enable or disable this button when the form is first opened and when the user moves from one record to another. The event which occurs when this happens is the On Current event for the form. Display the Event Properties for the form and click the ellipsis next to the On Current event to open a code stub in the VBA editor. Enter the following code: Private Sub Form_Current() If Me.NewRecord = True Then cmdPrevRec.SetFocus cmdNextRec.Enabled = False End If End Sub How does this code work? The Me object refers to the current form. Me.NewRecord refers to the form’s NewRecord property which has two settings: True The form is currently displaying a new record False The form is not currently displaying a new record We use the value of this property to find out whether the current record is a new record. If it is, we set the focus to the previous record command button (note you should use the name you have given to this button in your code) and set the Enabled property of the next record button to False. Change to form view and try out the buttons. When you click the new record button your next record button should grey out. 533574482 Page 3 of 10 Database Design 3 Hands-on Excercises So it seems that we have successfully prevented the user from using this button when they are in a new record. However, although we have successfully deactivated the button when the form is displaying a new record, when we go back to earlier records, the button remains disabled. We need to add code to re-enable the button when the form is not displaying a new record. As the form’s Current event takes place each time the user changes it to a new record we can do this by adding code to the event procedure we wrote above. The final procedure is shown below. The code you need to add is highlighted. Private Sub Form_Current() If Me.NewRecord = True Then cmdPrevRec.SetFocus cmdNextRec.Enabled = False Else cmdNextRec.Enabled = True End If End Sub There are now two ‘branches’ to the decision made by the progam. The code following If is executed if the NewRecord property is True and the code following the Else is executed if it is false. Note that we don’t need to specifically check whether the property is false. As it can only have one of two possible values, it must be false if it isn’t true. Previous record button We will now add code to the procedure to grey out the previous record button when the user is in the first record. The code you need to add to do this is highlighted below: Private Sub Form_Current() If Me.NewRecord = True Then cmdPrevRec.Enabled = True cmdPrevRec.SetFocus cmdNextRec.Enabled = False ElseIf Me.CurrentRecord = 1 Then cmdNextRec.Enabled = True cmdNextRec.SetFocus cmdPrevRec.Enabled = False Else cmdNextRec.Enabled = True cmdPrevRec.Enabled = True End If End Sub Notice that in this code there are three decisions to make: The form is displaying a new record The form is displaying the first record in the recordset Neither of the above. To choose between three alternatives we need to use Else If. 533574482 Page 4 of 10 Database Design 3 Hands-on Excercises The condition we test to see whether the form is in the first record is the CurrentRecord property. This property represents the current record number. If the form is displaying the first record, the value of this will be 1. If the form is in the first record we enable the next record button and set the focus to that button and then disable the previous record button. We also need to add a line of code to the first test. If we are in a new record we need to set the enabled property of the previous button to true. Finally, we need to make sure both buttons are enabled when we are not in the first or last record so we need to set the enabled property for both buttons to True following the Else. Interface and Database Engine errors Default error handling First we will look at some errors that might occur on a form and at the default Access error messages generated by these errors. Open the form frmEmployees and try the following: Enter a new record without an employee first name. You should see the following error message: Enter both names but type a title that is not in the combo list (Sir, for example). You should see the following error message: Enter a date later than today’s date in the employee start date field. You should see the following error message: None of these messages are very user-friendly and would be likely to confuse an inexperienced user. Our task is to trap these errors and deal with them in a more helpful way. 533574482 Page 5 of 10 Database Design 3 Hands-on Excercises Trappable Errors Each error that we can write our own custom error handling routine for is known as a trappable error. Each of these has a unique number associated with it. Access uses this number to display the default error message (like the messages in the examples above). You can get a complete list of thes errors by typing “Trappable errors” into the VBA help. If you want to know the error number of a particular error, click the Help button in the error message box. Try this for the errors you generated above. Display the error number for an error To handle these errors we need to use the On Error event property of the form. We will first write some VBA code to display these error numbers in a message box. Change the form to design view and select the On Error event property. Enter the following code: Private Sub Form_Error(DataErr As Integer, Response As Integer) MsgBox DataErr End Sub You only needed to enter one line of code between the start and end lines of the code stub. Note that the procedure has two arguments, both numbers. The DataErr argument is the error code of the error that occurred. The code displays its value in the message box. The Response argument tells Access how we want to deal with the error. It can have two possible values represented by the constants acDataErrDisplay which displays the default error message and acDataErrContinue which suppresses the default message and allows us to replace it with our own custom error handling. Its default value is acDataErrDisplay so as we have not given it a value in the code above, the default error message will be displayed after the message box shows us the error number. Change back to form view and make the same data entry errors. This time you will see a message box that displays the number associated with each error. This is the message box you will see when you enter a value that is not in the combo list, for example. Make a note of the number for each error as you will use it in the error handling code below. Once you click OK in the message box, Access will display the default error message as described above. Custom error handling code We will now add code to the form’s Error event to handle each of the above errors and also provide a default error handler for unforeseen events that might occur. 533574482 Page 6 of 10 Database Design 3 Hands-on Excercises Enter the code as shown below into the code stub for the form’s Error event. (Note you can’t have two different sub procedures associated with the same event so you will need to use the same code stub as you used for the message box procedure above. Private Sub Form_Error(DataErr As Integer, Response As Integer) Select Case DataErr Case 2237 MsgBox "You have entered a title that is not in the list. Click the list and choose one of the options shown." Response = acDataErrContinue Case 3314 MsgBox "You must enter both first and last name for the employee. Fill in the missing value." Response = acDataErrContinue Case 3317 MsgBox "Employee start date must be today's date or earlier. Please enter a valid date" Response = acDataErrContinue Case Else 'the default error handler displays VBA error message Response = acDataErrDisplay End Select End Sub We have used a Case statement in this procedure. It would be possible to write it using a sequence of If... Else statements but the code would not be as clear. (Try writing an alternative version using If to prove it for yourself.) Each Case uses the error values that we noted from the message boxes created earlier. For each of the three errors we want to handle we have set the value of Response to the constant acDataErrContinue which means that the default error message will not be displayed and we can replace it with a more informative message of our own. If an error that we had not anticipated occurs, the program will drop through to the final Case Else where the value of response is set to acDataErrDisplay and the default error message will be shown. VBA Errors To deal with VBA runtime errors you need to include error handling in your procedures. You have already seen examples of this in the VBA-driven command buttons created by the Control Wizard. An example of the code for a button to move to the next record is shown below. The lines in the code which relate to error handling are highlighted. As you can see, this is most of the code! 533574482 Page 7 of 10 Database Design 3 Hands-on Excercises Private Sub cmdNextRec_Click() On Error GoTo Err_cmdNextRec_Click DoCmd.GoToRecord , , acNext Exit_cmdNextRec_Click: Exit Sub Err_cmdNextRec_Click: MsgBox Err.Description Resume Exit_cmdNextRec_Click End Sub How does the error handling in this code work? It uses a rather outdated programming concept of GoTo associated with a label. Program statements ending in : are labels, not lines of executable code. There are two labels in the code above. Which lines of code are they? The On Error statement in the first line of code announces our intention to handle errors in this procedure. On error can be used in two ways: GoTo, as in the code above which tells the program to jump to the specified label, or Resume Next which tells the program to continue with the next line of code after the line that generated the error. The label can have any name but conventionally it is created by attaching Err_ to the start of the procedure name as in the Wizard-generated code above. If an error occurs, the procedure jumps to the label Err_cmdNextRec_Click and executes the lines that follow. These lines display an error message and then use the Resume statement to return the program to the Exit_cmdNextRec_Click label which ends the sub procedure. The error message is displayed in a message box. It uses the Description property of the Err object to do this. The Err object also has a Number property which displays the number of the error. The description is probably more helpful to users! If no error occurs, the procedure executes the DoCmd.GoToRecord statement, ignores the label on the following line as it is just a place marker and exits from the sub procedure. If we did not include the exit statement here, the program would simply continue to execute the error handling code as well which is obviously not what we want to happen. Create an error handler We will now use the same method as the Control Wizard code discussed above to create our own error handler. Enter the following code in the modTest standard module that you created earlier. Public Sub TestErrors() Dim dblResult As Double dblResult = 10 / InputBox("Enter a number") MsgBox "The result is " & dblResult End Sub This is just a simple procedure that uses an input box to get a number from the user and then displays the result of dividing 10 by that number in a message box. You can run the code by pressing F5 with the insertion point anywhere inside the procedure. First enter 5 in the input box. 533574482 Page 8 of 10 Database Design 3 Hands-on Excercises Press OK and the message box will display the result of the calculation 10 / 5 (2, of course!) Run the procedure again but this time enter 0 in the input box. As you should know, division by zero is an illegal operation so the code generates an error and you are thrown back into the VBA editor with the following error message displayed. If you click the Debug button, the line of code that generated the error will be highlighted. (Run > Reset will remove the highlight.) Run the procedure again and this time type the number two (as a word) into the input box. This generates a different runtime error – type mismatch – as the program is expecting a number, not a text string. 533574482 Page 9 of 10 Database Design 3 Hands-on Excercises The two errors generated above are the ones most likely to occur when this procedure is run so we will now create an error handler to deal with these two errors with a default case for any errors that we have not anticipated. Here’s the error handling code you need to add to the procedure. Err_TestErrors: Select Case Err.Number Case 11 'division by zero dblResult = 0 Resume Next Case 13 'type mismatch Resume Case Else MsgBox Err.Number & ": " & Err.Description Resume Exit_TestErrors End Select You’ll also need to add an On Error GoTo statement at the start of the procedure and an Exit label with associated code. Use the previous example to help you with this. The code uses a Case statement based on the error number – Err.Number. Notice that we have used comments to explain what each error number refers to. For the division by zero error we set the result to zero and resume the code at the next line which outputs the result in the message box. For the type mismatch error we simply try again. Any other error will simply exit from the procedure. Once you have tested this error handler you could try modifying it to include a message to indicate to the user the type of error they have made. You could also try running the code from a button on a form. Summary In this exercise you have: Created custom error handling procedures for trappable errors Added VBA error handling to procedures Handling errors in an informative and user-friendly way is an important part of any application you create. 533574482 Page 10 of 10