| < Free Open Study > |
|
There appears to be no better place to discuss error handling than in this chapter on debugging. I have already stressed the point of placing error handlers in every method that could possibly encounter an error.
Visual Studio .NET introduces true structured error handling. The error-handling methodology that was used in previous versions of Visual Basic will still work in VB .NET—you just can't mix the new Try/Catch structured handling with the old On Error GoTo ErrHandler construct in the same procedure. Most of my peers decry the evils of the GoTo statement. I certainly understand the shortcomings of GoTo, but I've never shared their total disdain for it. I, along with others, believe there are places for GoTo, albeit not many. VB is missing a "basic" statement, Continue, which allows you to go to the bottom of a For/Next, Do/Loop, and While/Wend loop. Without this statement, which C/C++ and other languages such as Clipper (dBase Compiler) have, GoTo provides the only easy way to get to the bottom of a loop. Sometimes, a loop that is already nested to several levels can only be made more complex by trying to maintain a structured approach for getting to the bottom of it. Well, enough of that! I'm beating a dead horse here. Regardless of your stand on the old syntax, assuming you programmed in earlier versions of VB, the code in this book uses the new structured error-handling code.
Because this book is not meant to explain all of the new things in .NET, I won't go into a lot of detail about the Try/Catch coding construct. There are two ways that I normally use error-handling code. First, I might include the whole procedure's code in a Try/Catch sequence. Listing 4-1 illustrates this approach.
Listing 4-1: Protecting the Whole Procedure
Public Sub CatchAnyProcedureError() Dim s As String Dim I As Integer Try For i = 1 to 10 S = s & "a" Next i ' add as many lines as needed Catch e As System.Exception MsgBox("We encountered an error: " & e.Message) End Try End Sub
Obviously, the method in Listing 4-1 does nothing but illustrate how to protect the whole subroutine in case of an error. The second way that I normally use error-handling code is to specifically catch errors in a small section of a method where I might try opening a file and want to take action within the context of that small section of code. The section of code shown in Listing 4-2 illustrates a sequence of code in which an error will be raised if the file name being erased does not exist. Likewise, if the new file cannot be created or written to, an error will be raised. You must always protect code that does file handling.
Listing 4-2: Selective Error Handling
Private Sub Test2() Dim count As Integer Dim fileHandle As Integer ' might have a code sequence here '.... '.... For count = 1 To 5 fileHandle = FreeFile() ' if the file being killed does not exist, an error ' will be raised. ' also, if a file can't be opened, ignore and try the next Try Kill("TEST" & count & ".TXT") FileOpen(fileHandle, "TEST" & count & ".TXT", OpenMode.Output) PrintLine(fileHandle, "This is a sample.") FileClose(fileHandle) Catch e As System.Exception ' ignore the error End Try Next ' more code... '... End Sub
Again, the code does not really do anything or make a lot of sense, but it does illustrate the protection of a small block of code within a larger procedure.
Because this book's topic is writing add-ins, let's add more functionality to the test add-in you created and enhanced in Chapters 2 and 3 to automate insertion of error-handling code.
First, you will add a new method to the Connect class named GenLocalErrorTrap, which is shown in Listing 4-3. This new method will retrieve the selected procedure and insert a Try/Catch error-handling construct around all of the executable code in the procedure. It does this by looking for the first line of executable code in the procedure and then inserting the Try statement. It then looks for the End Sub or End Function. Upon finding it, it will insert the Catch statement prior to writing the End statement to the output string. It will then put the code back into the active code window.
Listing 4-3: GenLocalErrorTrap Method
Shared Sub GenLocalErrorTrap() Dim sLine As String Dim sTemp As String Dim sTemp2 As String Dim sWord As String Dim i As Long Dim nL As Integer Dim bFound As Boolean Dim sTempLine As String Dim sProcType As String Dim sProcName As String Dim bFoundDefLine As Boolean Try sTemp = GetCodeFromWindow() sTemp2 = "" nL = MLCount(sTemp, 0) bFound = False For i = 1 To nL ' look for the first line of code sLine = MemoLine(sTemp, 0, i) ' get the procname to make the goto label unique If Not bFoundDefLine Then If InStr(sLine, "Sub ") > 0 Then sProcType = "Sub" ElseIf InStr(sLine, "Function ") > 0 Then sProcType = "Function" Else sTemp2 = sTemp2 & sLine & vbCrLf GoTo JustOutPutThisLine End If bFoundDefLine = True sTempLine = sLine Do While Trim$(sTempLine) <> "" sWord = GetToken(sTempLine, "_") ' when we find the Proc type, term the loop ' and retrieve the name next below If sWord = "Sub" Or sWord = "Function" Then Exit Do If Trim$(sWord) = "" Then Exit Do sProcName = sWord Loop sProcName = GetToken(sTempLine, "_") End If If Not bFound Then If InStr(sLine, "Sub ") > 0 Or _ InStr(sLine, "Function ") > 0 Or _ InStr(sLine, "Global ") > 0 Or _ InStr(sLine, "Const ") > 0 Or _ InStr(sLine, "Dim ") > 0 Then sTemp2 = sTemp2 & sLine & vbCrLf ElseIf Left$(Trim$(sLine), 1) = "'" Then sTemp2 = sTemp2 & sLine & vbCrLf ElseIf Trim$(sLine) = "" Then sTemp2 = sTemp2 & sLine & vbCrLf Else bFound = True sTemp2 = sTemp2 & vbCrLf & " Try" & vbCrLf If Trim$(sLine) <> "End " & sProcType Then Tsemp2 = sTemp2 & sLine & vbCrLf Else sTemp2 = sTemp2 & _ " Catch e as System.Exception" & vbCrLf sTemp2 = sTemp2 &_ " MsgBox(" & Chr(34) & "Error in " & _ sProcName & ": " & Chr(34) & " & _ e.Message)" & vbCrLf sTemp2 = sTemp2 & " End Try" & vbCrLf sTemp2 = sTemp2 & sLine & vbCrLf End If End If Else If InStr(sLine, "End " & sProcType) > 0 Then sTemp2 = sTemp2 & _ " Catch e as System.Exception" & vbCrLf sTemp2 = sTemp2 & _ " MsgBox(" & Chr(34) & "Error in " & _ sProcName & ": " & Chr(34) & " & _ e.Message)" & vbCrLf sTemp2 = sTemp2 & " End Try" & vbCrLf sTemp2 = sTemp2 & sLine & vbCrLf Exit For Else sTemp2 = sTemp2 & sLine & vbCrLf End If End If JustOutPutThisLine: Next ' now the proc with err code added is ready to paste PutCodeBack(sTemp2) Exit Sub Catch e As System.Exception MsgBox("Error in GenLocalErrorTrap: " & e.Message) End Try End Sub
Next, you will insert another supporting method named GetToken, which is shown in Listing 4-4. This is a parsing function that retrieves the next token from a source line. The comments in the code describe how this function is called. You can pass it a delimiter or a set of characters that are not delimiters. This function is another of the library procedures provided with this book's code.
Note | GetToken consumes the source string, which is passed by reference. This means that as token is returned, the token is removed from the front of the string. Therefore, if you want to maintain the original string, you must save a copy of it before your first call to GetToken. |
Listing 4-4: GetToken
Shared Function GetToken(ByRef srcline As String, _ ByVal rsNonDelimiters As String, _ Optional ByVal rsDel As String = "N") _ As String '----- ' If rsDel = "N" then the rsNondelimiters is a list of non delimiters ' which is added to a list of AN Chars (a-z, A-Z, 0-9), which are ' always assumed to be non delimiters. ' If rsDel="D" then rsNonDelimiters is the list of delimiters, anything ' else in the string is assumed to be non delimiter. ' Get Next word from srcLine. An alphanumeric and any character ' found in strDelimtrs is a valid char for the word. i.e. a char ' which is not alphanumeric and not found in the delimiter string ' will terminate the word. If space is not a delimiter it must be ' included in the strNonDelimitrs. ' Typical call is: ' srcLine = GetToken(srcLine, " ().!" or ' srcLine = GetToken(srcLine, " ,") ' where space and are the delimiters ' Any non alphanumeric and not in the " ().!" would terminate the string ' To include " in the set of allowable chars, concatenate ' chr(34) with the' other non delimiters. ' If non delimiters are not supplied, don't compare for them ' and performance is increased... '----- Dim n_w As String ' staging area for return string Dim FC As String ' first char of string Dim lsTemp As String Dim lsTemp2 As String Const AN_DIGITS = "abcdefghijklmnopqrstuvwxyz" & _ "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" Try n_w = "" If rsDel = "N" Then lsTemp2 = AN_DIGITS & rsNonDelimiters Else lsTemp2 = rsNonDelimiters End If Do While Trim$(srcline) <> "" FC = Left$(srcline, 1) lsTemp = "*" & FC & "*" If rsDel = "N" Then If Not (lsTemp2 Like lsTemp) Then ' save all but first char for next call srcline = Mid(srcline, 2) If Trim$(n_w) <> "" Then GetToken = n_w Exit Function End If Else n_w = n_w & FC srcline = Mid(srcline, 2) End If Else If (lsTemp2 Like lsTemp) Then ' save all but first char for next call srcline = Mid(srcline, 2) If Trim$(n_w) <> "" Then GetToken = n_w Exit Function End If Else n_w = n_w & FC srcline = Mid(srcline, 2) End If End If Loop GetToken = n_w Exit Function Catch e As System.Exception MsgBox("Error in GetToken: " & e.Message) GetToken = n_w End Try End Function
Next, you add another node to the TreeView named Proc Error Handler, as shown in Figure 4-11. Follow the same procedure for adding a child node to the TreeView that you learned in Chapter 3.
Figure 4-11: Adding the Proc Error Handler node to the TreeView
Finally, add a new Case statement to the AfterSelect event in the frmTreeView. This new code is shown in bold in Listing 4-5.
Listing 4-5: Error Handling in the AfterSelect Event
Private Sub tvMenu_AfterSelect(ByVal sender As Object, _ ByVal e As System.Windows.Forms.TreeViewEventArgs) _ Handles tvMenu.AfterSelect Dim i As Integer Select Case UCase$(e.Node.Text) Case "SMART DESKTOP" 'ignore root clicks Case "BLOCK COMMENT" Call Connect.BlockComment() Case "UNCOMMENT" Call Connect.BlockUnComment() Case "BLOCK CHANGE" Call Connect.BlockChange() Case "BLOCK DELETE" Call Connect.BlockDelete() Case "PROC ERROR HANDLER" Call Connect.GenLocalErrorTrap() Case Else MsgBox("Please click on a Child Node.", _ MsgBoxStyle.Information, "Unknown Request") End Select End Sub
After all of the code has been successfully added to the add-in, press F5 to test the new code. A second version of Visual Studio .NET will start. I have created a standard Windows application (WindowsApplication2) to use in testing the add-in. It has several procedures of dummy code, which you will find in the code for this chapter. Once you have selected the test project, go to the Add-in Manager and connect the add-in. Next, select Tools ⇒ Smart Desktop. This will cause the add-in menu TreeView form to be loaded. Open the nodes of the TreeView. Finally, select the whole Sub Test, as shown in Figure 4-12, and click the Proc Error Handler TreeView node.
Figure 4-12: Select the procedure to receive an error handler.
The error handler will be automatically added to the procedure, as shown in Figure 4-13. A simple exercise that you can try on your own would be to clone the GenLocalErrorTrap method, rename the cloned method, and modify it to block only a selected block of code. You would then add another node to the TreeView menu and add a call to the newly cloned method in the AfterSelect event of the TreeView.
Figure 4-13: Error handler placed in Sub Test()
Again, in this enhancement to the add-in, you have been able to add new functionality, or business logic, without having to have any interface to the .NET extensibility model. By building and reusing the same basic library methods (GetCodeFromWindow and PutCodeBack), you are able to concentrate on the creative part of building add-in functionality, which manipulates blocks of code in the text editor of Visual Studio .NET. Believe me, there are unlimited things you can do with the selected code block once you have it extracted into a string. All you have to do is put your thinking cap on (or keep it on). In VBCommander, I have added well over 100 new features to the VB 6.0 IDE. Almost every one of them was created as a result of encountering something repetitive that I needed to automate when operating with blocks of code. Necessity really is the mother of invention.
| < Free Open Study > |
|