Related
In the following code, we occasionally get collision errors. If it would wait a second and retry, it would go through.
I want it to try once. If it fails, log the error and retry. If it fails 3 times, MsgBox to the user, give up and return. The only way I can think of is using a GOTO back to the ExeHandler. Seems there should be a better way.
Public Function RunADO(strContext As String, strSQL As String, Optional intErrSilent As Integer = 0, Optional intErrLog = -1) As Integer
On Error GoTo ErrHandler
ExeHandler:
PostToLog strContext, "SQL: " & strSQL
CurrentProject.Connection.Execute strSQL, dbFailOnError
RunADO = -1
Exit Function
ErrHandler:
RunADO = 0
If intErrSilent = 0 Then
MsgBox Err.Number & ": " & Err.Description, vbCritical, "Run ADO"
End If
If intErrLog = -1 Then
PostErrorToLog Err.Number, strContext, Err.Source & ":" & Err.Description & ": " & "SQL: " & strSQL
End If
End Function
Perhaps something like this...
Option Explicit
Private Const MaxRetries As Long = 3
Public Function RunADO(strContext As String, strSQL As String, Optional intErrSilent As Integer = 0, Optional intErrLog = -1) As Integer
Dim attemptCounter As Long
For attemptCounter = 1 To MaxRetries
RunADO = RunADOInternal(strContext, strSQL, intErrSilent, intErrLog)
If RunADO = -1 Then
Exit Function
End If
If intErrSilent = 0 And attemptCounter >= MaxRetries Then
MsgBox Err.Number & ": " & Err.Description, vbCritical, "Run ADO"
RunADO = 0
Exit Function
End If
If intErrLog = -1 Then
PostErrorToLog Err.Number, strContext, Err.Source & ":" & Err.Description & ": " & "SQL: " & strSQL
End If
Application.Wait (Now + TimeValue("0:00:01"))
Next attemptCounter
End Function
Private Function RunADOInternal(strContext As String, strSQL As String, Optional intErrSilent As Integer = 0, Optional intErrLog = -1) As Integer
On Error GoTo ErrHandler
PostToLog strContext, "SQL: " & strSQL
CurrentProject.Connection.Execute strSQL, dbFailOnError
RunADO = -1
Exit Function
ErrHandler:
RunADO = 0
End Function
Consider using a Do / Loop statement that ends when the maximum number of attempts have been made.
Public Function RunADO(strContext As String, _
strSQL As String, _
Optional intErrSilent As Integer, _
Optional intErrLog = -1) As Integer
Dim Attempts As Integer
Do While Attempts < 3
On Error Resume Next
PostToLog strContext, "SQL: " & strSQL
CurrentProject.Connection.Execute strSQL, dbFailOnError
If Err.Number Then
If intErrSilent = 0 Then
MsgBox Err.Number & ": " & Err.Description, vbCritical, "Run ADO"
End If
If intErrLog = -1 Then
PostErrorToLog Err.Number, strContext, _
Err.Source & ":" & Err.Description & ": " & "SQL: " & strSQL
End If
Attempts = Attempts + 1
Else
RunADO = -1
Exit Do
End If
Loop
End Function
I have a vba code that scan image from scanner , the code works and doesnt have any problem with type hp an brother scanner but when I used it with canon can not find the scanner and send message no wia device. How can solve this problem
Private Sub Command10_Click()
Const wiaFormatJPEG = "{B96B3CAE-0728-11D3-9D7B-0000F81EF32E}"
On Error GoTo Handle_Err
Dim Dialog1 As New WIA.CommonDialog, DPI As Integer, PP As Integer, l As Integer
Dim Scanner As WIA.Device
Dim img As WIA.ImageFile
Dim intPages As Integer
Dim strFileJPG As String
Dim blnContScan As Boolean ' to activate the scanner to start scan
Dim ContScan As String 'msgbox to chk if more pages are to be scanned
Dim strFilePDF As String
Dim RptName As String
Dim strProcName As String
strProcName = "ScanDocs"
DoCmd.SetWarnings False
DoCmd.RunSQL "delete from scantemp"
DoCmd.SetWarnings False
blnContScan = True
intPages = 0
Do While blnContScan = True
DPI = 200
PP = 1 'No of pages
Set Scanner = Dialog1.ShowSelectDevice(WIA.WiaDeviceType.ScannerDeviceType, True, False)
Set img = Dialog1.ShowTransfer(Scanner.Items(1), wiaFormatJPEG, True)
strFileJPG = ""
intPages = intPages + 1
strFileJPG = "\\User-pc\saveimage\" & num & Trim(str(intPages)) & ".jpg"
img.SaveFile (strFileJPG)
DoCmd.RunSQL "insert into scantemp (picture) values ('" & strFileJPG & "')"
DoCmd.SetWarnings False
Set Scanner = Nothing
Set img = Nothing
' strFileJPG = ""
'Prompt user if there are additional pages to scan
ContScan = MsgBox("?save another page ", vbQuestion + vbYesNoCancel)
If ContScan = vbNo Then
blnContScan = False
ElseIf ContScan = vbCancel Then
DoCmd.RunSQL "delete from scantemp where picture = '" & strFileJPG & "'"
End If
'''''''''''''''
Loop
Dim Image_Path As String
GoTo StartPDFConversion
StartPDFConversion:
Dim s As String
strFilePDF = "\\User-pc\saveimage\" & (num) & ".pdf"
RptName = "rptScan"
DoCmd.OpenReport RptName, acViewReport, , , acHidden
DoCmd.Close acReport, RptName, acSaveYes
DoCmd.OutputTo acOutputReport, RptName, acFormatPDF, strFilePDF
Me.imgp = strFilePDF
DoCmd.RunSQL "delete from scantemp" 'delete all data from table scantemp after converted it to pdf
'/*******************************\
'/********************************************\
Handle_Exit:
Exit Sub
Handle_Err:
Select Case Err.Number
Case 2501
Resume Handle_Exit
Case Else
MsgBox "the." & vbCrLf & vbCrLf & _
"In Function:" & vbTab & strProcName & vbCrLf & _
"Err Number: " & vbTab & Err.Number & vbCrLf & _
"Description: " & vbTab & Err.Description, 0, _
"Error in " & Chr$(34) & strProcName & Chr$(34)
Resume Handle_Exit
End Select
Exit Sub
End Sub
Option Compare Database
Private Declare Function TWAIN_AcquireToFilename Lib "TWAIN32d.DLL" (ByVal hwndApp As Long, ByVal bmpFileName As String) As Integer
Private Declare Function TWAIN_IsAvailable Lib "TWAIN32d.DLL" () As Long
Private Declare Function TWAIN_SelectImageSource Lib "TWAIN32d.DLL" (ByVal hwndApp As Long) As Long
Private Sub cmdScan_Click()
Dim Ret As Long, PictureFile As String
Dim intPages As Integer
Dim blnContScan As Boolean
Dim ContScan As String 'msgbox to chk if more pages are to be scanned
blnContScan = True
intPages = 0
Do While blnContScan = True
DPI = 200
PP = 1 'No of pages
intPages = intPages + 1
PictureFile = CurrentProject.Path & "\" & myfolder & "\" & Me.number & Trim(Str(intPages)) & ".jpg"
Ret = TWAIN_AcquireToFilename(Me.hwnd, PictureFile)
ContScan = MsgBox("? ÍÝÙ ÕæÑÉ ÇÎÑì ", vbQuestion + vbYesNo, "ÊäÈíÉ")
If ContScan = vbNo Then
blnContScan = False
End If
Loop
I can't figure out how to handle the error raised in my macro.
Through an application.Vlookup I search for a value. The problem is, if that value doesn't exist macro stops.
I tried a On Error Resume Next which works fine, but I would like to tell the user the value doesn't exist.
Private Sub CommandButton1_Click()
Dim Num As Double
Dim Cle As Integer
Dim Dpt As String
Dim Age As Integer
Dim Essaidate As String
Dim CommNaiss As String
Dim NumOrdre As String
Dim Reg As String
'Initialisons la date du jour
CeJour = Date
Num = TextBox1.Text
Cle = 97 - (Num - (Int(Num / 97) * 97))
If Cle < 10 Then
Label2.Caption = "0" & Cle
Else
Label2.Caption = Cle
End If
If Mid(TextBox1.Text, 1, 1) = "1" Then
Label4.Caption = "Masculin"
Else
Label4.Caption = "Féminin"
End If
Essaidate = "1" & "/" & Mid(TextBox1, 4, 2) & "/" & "19" & Mid(TextBox1, 2, 2)
'MsgBox ("La date de naissance (sans le jour) de cette personne est :" & Essaidate)
Dpt = Application.VLookup(Mid(TextBox1.Text, 6, 2), Range("M1:N96"), 2, False)
Label6.Caption = Dpt & " (" & Mid(TextBox1.Text, 6, 2) & ")"
Reg = Application.VLookup(Mid(TextBox1.Text, 6, 2), Range("M1:O96"), 3, False)
Label15.Caption = Reg
'On Error Resume Next
CommNaiss = Application.VLookup(CLng(Mid(TextBox1.Text, 6, 5)), Range("AV1:AW36529"), 2, False) 'That's the line I get an error if value does't exist....
I would use a GoTo ErrorHandler:, have a MsgBox, then resume next.
On Error GoTo ErrorHandler
ErrorHandler:
MsgBox "Value does not exist"
Resume Next
Tim's answer - using an error handler is best but if you want to use on error resume next then you can use IsError:
On Error Resume Next
CommNaiss = Application.VLookup(CLng(Mid(TextBox1.Text, 6, 5)), Range("AV1:AW36529"), 2, False)
if IsError(CommNaiss) then msgbox("value not found")
On Error Goto 0 ' remember to turn on error resume next off again
here follow two possible ways
1) "On Error..." way
On Error Resume Next
Dpt = Application.VLookup(Mid(TextBox1.Text, 6, 2), Range("M1:N96"), 2, False)
On Error GoTo 0
If Dpt = "" Then
MsgBox "Value : " & Mid(TextBox1.Text, 6, 2) & " not found in Range(""M1:N96"")"
Else
Label6.Caption = Dpt & " (" & Mid(TextBox1.Text, 6, 2) & ")"
End If
2) "Find" way
Dim found As Range
Set found = Range("M1:M96").Find(What:=Mid(TextBox1.Text, 6, 2), LookIn:=xlValues, LookAt:=xlWhole)
If found Is Nothing Then
MsgBox "Value : " & Mid(TextBox1.Text, 6, 2) & " not found in Range(""M1:N96"")"
Else
Label6.Caption = Dpt & " (" & Mid(TextBox1.Text, 6, 2) & ")"
End If
and the same for Reg
This any good? -
CommNaiss = Application.WorksheetFunction.IfError( _
Application.WorksheetFunction.VLookup(CLng(Mid(TextBox1.Text, 6, 5)) _
, Range("AV1:AW36529"), 2, False), "Error")
No need to add On-Error-GoTo because the VLookup function doesn't throw error but it returns it. Try to declare the variable Dpt as Variant and check with IsError if the VLookup returned an error.
Sub test()
Dim Dpt As Variant
Dpt = Application.VLookup("searched-text", Range("A1:C3"), 2, False)
If IsError(Dpt) Then
MsgBox "Error '" & DecodeError(Dpt) & "' occured.", vbCritical
End If
End Sub
Here an example of function which will decode the error number returned by VLookup to string with description.
Private Function DecodeError(ByVal error As Variant) As String
On Error Resume Next
Select Case CLng(error)
Case xlErrDiv0
DecodeError = "#DIV/0!"
Case xlErrNA
DecodeError = "#N/A"
Case xlErrName
DecodeError = "#NAME?"
Case xlErrNull
DecodeError = "#NULL!"
Case xlErrNum
DecodeError = "#NUM!"
Case xlErrRef
DecodeError = "#REF!"
Case xlErrValue
DecodeError = "#VALUE!"
Case Else
DecodeError = "Unknown error"
End Select
End Function
I am trying to create a pop up question in powerpoint VBA, so far so good. But below code doesn’t seem to work. Idea is that you get a popup box with value to enter between 100 - 200 (inclusive). But must enter a value between or can accept failed as input. The inputbox cannot be cancelled or null/empty responses. The inner loop (loop 1) seems to work ok, but if I enter 150 it doesn't terminate the loop 2 instead keeps going unless type failed but it stops with any text rather than only "failed".
Sub OnSlideShowPageChange(ByVal SSW As SlideShowWindow)
'Declare Variables
Dim xType, xLimitHi, xLimitLo, xPrompt As String
Dim InputvarTemp As String
Dim msgResult As Integer
xLimitHi = 200
xLimitLo = 100
xPrompt = "Enter Value between 100 and 200 (Inclusive)"
Do 'loop 2 check within limit or failed
msgResult = vbNo
Do 'loop 1 check Empty / Null or Cancelled input
InputvarTemp = InputBox(xPrompt, xPrompt)
If StrPtr(InputvarTemp) = 0 Then ' Check if cancelled is pressed
MsgBox "Invalid Input - Cannot be cancelled", 16, "Invalid Input."
Else
If Len(InputvarTemp) = 0 Then ' Check Null response
MsgBox "Invalid Input - Cannot be Empty / Null ", 16, "Invalid Input."
Else
msgResult = MsgBox("You have Entered " & InputvarTemp, vbYesNo + vbDefaultButton2, "Check Value in between " & xLimitLo & " to " & xLimitHi & "(Inclusive)")
If CDec(InputvarTemp) < 100 Or CDec(InputvarTemp) > 200 Then ' Check within Limits
MsgBox "Invalid Input - Not Within Limit", 16, "Invalid Input."
End If
End If
End If
Loop Until Len(InputvarTemp) > 0 And msgResult = vbYes And StrPtr(InputvarTemp) = 1 And IsNull(InputvarTemp) = False 'loop 1 check Empty / Null or Cancelled input
Loop Until CDec(InputvarTemp) >= 100 And CDec(InputvarTemp) <= 200 Or InputvarTemp = "Failed" 'loop 2 check within limit
Select Case InputvarTemp
Case "Failed"
MsgBox "Test Criteria Failed, Contact Production Engineer", 16, "Failed Test Criteria."
Case Else
MsgBox "Test Criteria Passed", 16, "Passed Test Criteria."
End Select
End Sub
Can anyone point me to the problem? Many thanks in advance. This is a part of a bigger code project but this part is not working I have isolated this code in to a single file to run by itself to figure out the issue.
To better understand what's going on, you need to write your code in such a way that it does as little as possible; right now you have a single procedure that does so many things it's hard to tell exactly what's going wrong and where.
Write a function to confirm user's valid numeric input:
Private Function ConfirmUserInput(ByVal input As Integer) As Boolean
ConfirmUserInput = MsgBox("Confirm value: " & CStr(input) & "?", vbYesNo) = vbYes
End Function
Then write a function to deal with user's input:
Private Function IsValidUserInput(ByVal userInput As String,_
ByVal lowerLimit As Double, _
ByVal upperLimit As Double) _
As Boolean
Dim result As Boolean
Dim numericInput As Double
If StrPtr(userInput) = 0 Then
'msgbox / cannot cancel out
ElseIf userInput = vbNullString Then
'msgbox / invalid empty input
ElseIf Not IsNumeric(userInput) Then
'msgbox / must be a number
Else
numericInput = CDbl(userInput)
If numericInput < lowerLimit Or numericInput > upperLimit Then
'msgbox / must be within range
Else
result = ConfirmUserInput(numericInput)
End If
End If
IsValidUserInput = result
End Function
This function can probably be written in a better way, but nonetheless it will return False if any of the validation rules fail, or if user doesn't confirm their valid input. Now you're equipped for looping, and since all the complex logic is extracted into its own function, the loop body gets pretty easy to follow:
Private Function GetTestCriteria(ByVal lowerLimit As Double, _
ByVal upperLimit As Double) As Boolean
Const failed As String = "Failed"
Dim prompt As String
prompt = "Enter Value between " & lowerLimit & _
" and " & upperLimit & " (Inclusive)."
Dim userInput As String
Dim isValid As Boolean
Do
userInput = InputBox(prompt, prompt)
isValid = IsValidUserInput(userInput, lowerLimit, upperLimit) _
Or userInput = failed
Loop Until IsValid
GetTestCriteria = (userInput <> failed)
End Sub
The OnSlideShowPageChange procedure can now look like this:
Private Sub OnSlideShowPageChange(ByVal SSW As SlideShowWindow)
If GetTestCriteria(100, 200) Then
MsgBox "Test criteria passed."
Else
MsgBox "Test criteria failed, contact production engineer."
End If
End Sub
I haven't tested any of this code, but I'm sure debugging these more specialized functions will be easier than debugging your monolithic chunk of code; by extracting these functions, you untangle the logic, and I bet the above does exactly what you're trying to do. Also note:
Dim xType, xLimitHi, xLimitLo, xPrompt As String declares xPrompt as a String, and everything else as a Variant. I don't think that's your intent here.
Select Case is best used with Enum values; use If-ElseIf constructs otherwise.
Slight modifications, per below comment:
how do i capture the user input to do something like write to a file
Now if you wanted to do something with the valid user inputs, say, write them to a file, you'd need GetTestCriteria to return the input - but that function is already returning a Boolean. One solution could be to use an "out" parameter:
Private Function GetTestCriteria(ByVal lowerLimit As Double, _
ByVal upperLimit As Double, _
ByRef outResult As Double) As Boolean
Const failed As String = "Failed"
Dim prompt As String
prompt = "Enter Value between " & lowerLimit & _
" and " & upperLimit & " (Inclusive)."
Dim userInput As String
Dim isValid As Boolean
Do
userInput = InputBox(prompt, prompt)
isValid = IsValidUserInput(userInput, lowerLimit, upperLimit, outResult) _
Or userInput = failed
Loop Until IsValid
GetTestCriteria = (userInput <> failed)
End Sub
Private Function IsValidUserInput(ByVal userInput As String,_
ByVal lowerLimit As Double, _
ByVal upperLimit As Double, _
ByRef outResult As Double) _
As Boolean
Dim result As Boolean
Dim numericInput As Double
If StrPtr(userInput) = 0 Then
'msgbox / cannot cancel out
ElseIf userInput = vbNullString Then
'msgbox / invalid empty input
ElseIf Not IsNumeric(userInput) Then
'msgbox / must be a number
Else
numericInput = CDbl(userInput)
If numericInput < lowerLimit Or numericInput > upperLimit Then
'msgbox / must be within range
Else
result = ConfirmUserInput(numericInput)
outResult = numericInput
End If
End If
IsValidUserInput = result
End Function
And now you can call a method in OnSlideShowPageChange, to write the valid result to a file:
Private Sub OnSlideShowPageChange(ByVal SSW As SlideShowWindow)
Dim result As Double
If GetTestCriteria(100, 200, result) Then
MsgBox "Test criteria passed."
WriteResultToFile result
Else
MsgBox "Test criteria failed, contact production engineer."
End If
End Sub
If you run into issues implementing this WriteResultToFile procedure, and existing Stack Overflow questions don't have an answer for you (slightly unlikely), feel free to ask another question!
Retailcoder's answer as a general approach is top notch. I would like to draw attention specifically to the use of IsNumeric() which would solve most issues. Currently your code fails if any non-numeric string is entered.
Had a look at the code to try and see if I could at least answer what was happening to try and appease your curiosity. You mentioned that it looked like you couldn't leave your second loop. In practice I was unable to exit your first loop. I'm sure was due to the StrPtr(InputvarTemp) = 1. I didn't even know what that was until I looked it up. In short it is an undocumented feature that was used to check if Cancel was pushed / get the underlying memory address of variables (apparently).
Before the end of the first loop I put this in for debugging
MsgBox Len(InputvarTemp) & " " & msgResult & " " & StrPtr(InputvarTemp) & " " & IsNull(InputvarTemp)
When I type "150" in the InputBox the results of the message box are as follows. The third value represent the StrPtr(InputvarTemp)
3 6 246501864 FALSE
246501864 is greater than 1 which would cause the loop exit to fail. Again, retailcoder has an excellent answer and I will not reinvent his wheel.
with thanks to #retailcoder and #Matt below is the completed code for any to use, your help is truly appropriated
Capture user input to a file(s) from Powerpoint presentation, using a Config.ini to minimize everyday programming (or no programming code to a standard user)
> Code in Slide 1
Option Explicit
Option Compare Text
Public WithEvents PPTEvent As Application
Public TimeNow, ToDate As String
Public WorkOrder, Serial, UserName As String
Public ReportFile, TempReportFile, TimingFile As String
Sub OnSlideShowPageChange(ByVal SSW As SlideShowWindow)
'Declare Variables
Dim ShellRun As Long
Dim INIPath, StartTime, EndTime, TimeDifferance As String ' from Enviorment
Dim PCPver, ModuleName, PCPFileName, Timed, ResultsFolder, TrainingFolder, TimeingFolder, TrainedFolder, xType, xPrompt, xvarUnit, y As String 'From INI file
Dim xLimitHi, xLimitLo As Variant
Dim result As Double
Dim FailedResult As Double
Dim PCPverInput, inputvar, InputvarDate, InputvarTrueFalse, InputvarGeneral, InputvarLimit, InputvarTemp As String 'From User
Dim TrainingFile, SelfCheck, InvalidCharacter1, InvalidCharacter2 As String 'Variables for Filenames
Dim msgResult, msgResultTemp As Integer
Dim myVarPass As Boolean
Dim KeyAscii As Integer 'Try and Hook Esc key
Dim ppApp As Object
Const fsoForAppend = 8
'Declare and create a FileSystemObject.
Dim fso, ResutlsFSO, TrainingFSO, TimeingFSO As Object 'Need Microsoft Script Runtime in references
' Declare a TextStream.
Dim oFile, ResutlsStream, TrainingStream, TimeingStream As Object
'Assign Variables
INIPath = ActivePresentation.Path & "\" & "Config.ini"
'ShellRun = Shell(ActivePresentation.Path & "\" & "Esc.exe")
SelfCheck = ActivePresentation.Name
ToDate = Format(Date, "dd-mmm-yyyy")
TimeNow = Replace(Format(time, "hh:mm:ss"), ":", "-")
StartTime = Format(time, "hh:mm:ss")
'Retrive Folderpaths and create file names
ModuleName = GetINIString("PCPInfo", "ModuleName", INIPath)
Timed = GetINIString("Options", "Timed", INIPath)
Set ResutlsFSO = CreateObject("Scripting.FileSystemObject")
Set TrainingFSO = CreateObject("Scripting.FileSystemObject")
Set TimeingFSO = CreateObject("Scripting.FileSystemObject")
'Retrive PCP version from Ini file
PCPver = GetINIString("PCPInfo", "PCPver", INIPath)
PCPFileName = GetINIString("PCPInfo", "PCPFileName", INIPath)
ResultsFolder = GetINIString("Folders", "ResultsFolder", INIPath)
TrainingFolder = GetINIString("Folders", "TrainingFolder", INIPath)
TimeingFolder = GetINIString("Folders", "TimeingFolder", INIPath)
TrainedFolder = GetINIString("Folders", "TrainedFolder", INIPath)
Do
If (SelfCheck <> PCPFileName) Then
MsgBox "Invalid Config.ini File. Replace with Correct INI file to continue. ", 16, "Invalid Config.ini File."
End If
Loop Until (SelfCheck = PCPFileName)
'Collect PCP version, User Name, Work Order, Serial Number
If (SSW.View.CurrentShowPosition = 1) Then
'Retrive PCP Version from BOM - User Input
Do
Do
PCPverInput = InputBox("Enter PCP Number including Version", "Enter PCP Number including Version")
If (Len(PCPverInput) < 4) Then
MsgBox "Invalid Input - PCP version cannot be Empty / Null / cancelled", vbOKOnly, "Invalid Input"
End If
Loop Until (Len(PCPverInput) > 4)
'Check PCPversion against BOM
If (PCPver <> PCPverInput) Then
'Display Warning Messages
MsgBox "Incorrect PCP version. Contact Team Leader / Product Engineer. Cannot Continue the programm", 16, "Incorrect PCP version."
End If
Loop Until (PCPver = PCPverInput)
'Retrive UserName - User Input
Do
msgResult = 7
Do
UserName = InputBox("Enter / Scan Operator Name", "Enter / Scan Operator Name")
msgResult = MsgBox("You have Enterd Operator Name " & UserName, vbYesNo + vbDefaultButton2, "Operator Name")
If (Len(UserName) < 4) Then
MsgBox "Invalid Input - User / Operator Name cannot be Empty / Null / cancelled", 16, "Invalid Input"
End If
Loop Until (Len(UserName) > 4) And (msgResult = vbYes)
Loop Until (Len(UserName) > 4)
'Retrive Work Order
Do
msgResult = 7
Do
WorkOrder = InputBox("Enter / Scan Work Order", "Enter / Scan Work Order")
msgResult = MsgBox("You have Enterd Work Order " & WorkOrder, vbYesNo + vbDefaultButton2, "Work Order")
If (Len(WorkOrder) < 4) Then
MsgBox "Invalid Input - Work Order cannot be Empty / Null / cancelled. Minimum 5 Numbers", 16, "Invalid Input"
End If
Loop Until (Len(WorkOrder) > 4) And (msgResult = vbYes)
Loop Until (Len(WorkOrder) > 4)
'Retrive Serial Number
Do
msgResult = 7
Do
Serial = InputBox("Enter / Scan Serial Number", "Enter / Scan Serial Number")
msgResult = MsgBox("You have Enterd Serial Number " & Serial, vbYesNo + vbDefaultButton2, "Serial Number")
If (Len(Serial) < 1) Then
MsgBox "Invalid Input - Serial Number cannot be Empty / Null / cancelled. Use -NOSERIAL- if Not Applicable", 16, "Invalid Input"
End If
Loop Until (Len(Serial) > 1) And (msgResult = vbYes)
Loop Until (Len(Serial) > 1)
If (Len(Dir(ResultsFolder, vbDirectory)) = 0) Then
MkDir ResultsFolder
End If
If (Len(Dir(ResultsFolder & "\" & WorkOrder, vbDirectory)) = 0) Then
MkDir ResultsFolder & "\" & WorkOrder
End If
If (Len(Dir(ResultsFolder & "\" & WorkOrder & "\" & Serial, vbDirectory)) = 0) Then
MkDir ResultsFolder & "\" & WorkOrder & "\" & Serial
End If
ReportFile = ResultsFolder & "\" & WorkOrder & "\" & Serial & "\" & PCPver & "_" & ToDate & "_" & TimeNow & ".txt"
Set ResutlsStream = ResutlsFSO.CreateTextFile(ReportFile, True)
ResutlsStream.WriteLine PCPver & " " & ModuleName & " Build / Test Checklist"
ResutlsStream.WriteLine "===================================================================================================="
ResutlsStream.WriteLine ""
ResutlsStream.WriteLine "Work Order :" & WorkOrder
ResutlsStream.WriteLine "Serial Number (if Applicable) :" & Serial
ResutlsStream.WriteLine "Test / Assembly Operator (Full Name) :" & UserName
ResutlsStream.WriteLine "Date (dd-mmm-yyyy) :" & ToDate
ResutlsStream.WriteLine ""
ResutlsStream.Close
If (Len(Dir(TrainingFolder, vbDirectory)) = 0) Then
MkDir TrainingFolder
End If
If (Len(Dir(TrainingFolder & "\" & UserName, vbDirectory)) = 0) Then
MkDir TrainingFolder & "\" & UserName
End If
TrainingFile = TrainingFolder & "\" & UserName & "\" & PCPver & ".csv"
If (Len(Dir(TrainingFile)) = 0) Then
Set TrainingStream = TrainingFSO.CreateTextFile(TrainingFile, True)
TrainingStream.WriteLine UserName & "'s " & ModuleName & " " & PCPver & " Training File"
TrainingStream.WriteLine "===================================================================================================="
TrainingStream.WriteLine "Operator" & Chr(44) & "PCP Version" & Chr(44) & "W/O" & Chr(44) & "Serial" & Chr(44) & "Date" & Chr(44) & "Time"
TrainingStream.WriteLine "===================================================================================================="
Else
Set TrainingStream = TrainingFSO.OpenTextFile(TrainingFile, 8)
End If
TrainingStream.WriteLine UserName & Chr(44) & PCPver & Chr(44) & WorkOrder & Chr(44) & Serial & Chr(44) & ToDate & Chr(44) & Format(time, "HH:MM:SS AM/PM")
TempReportFile = ReportFile
End If
'Detect Slide Number and Retrive Relevant Question from INI File
y = SSW.View.CurrentShowPosition
If (Len(y) > 0) Then
xType = GetINIString(SSW.View.CurrentShowPosition, "PromptType", INIPath)
If (Len(xType) > 0) Then
Set ResutlsStream = ResutlsFSO.OpenTextFile(TempReportFile, 8)
Select Case xType
Case "Message"
xPrompt = GetINIString(SSW.View.CurrentShowPosition, "Prompt", INIPath)
MsgBox xPrompt, vbYes, xPrompt
Case "Date"
xPrompt = GetINIString(SSW.View.CurrentShowPosition, "Prompt", INIPath)
Do
msgResult = 7
Do
inputvar = InputBox(xPrompt, "Enter Date")
InputvarDate = inputvar
msgResult = MsgBox("You have Enterd " & Format(inputvar, "dd-Mmm-yyyy") & " to " & xPrompt, vbYesNo + vbDefaultButton2, "Check Date Input")
If (StrPtr(inputvar) = 0) Or (Len(inputvar) < 6) Then
MsgBox "Invalid Date Input - Cannot be Empty / Null / cancelled. Enter a Valid date, in dd-Mmm-yyyy format", 16, "Invalid Input."
End If
inputvar = Format(inputvar, "dd-Mmm-yyyy")
If (Not IsDate(inputvar)) Then
MsgBox "Enter a Valid date, in dd-Mmm-yyyy format", 16, "Invalid Date."
End If
Loop Until (IsDate(inputvar) = True) And (msgResult = vbYes) And (Len(InputvarDate) > 6)
Loop Until (IsDate(inputvar) = True) And (msgResult = vbYes)
ResutlsStream.WriteLine "Step " & SSW.View.CurrentShowPosition & ". " & xPrompt & Chr(9) & ":" & Chr(9) & inputvar & " " & xvarUnit
Case "TrueFalse"
xPrompt = GetINIString(SSW.View.CurrentShowPosition, "Prompt", INIPath)
Do
msgResult = 7
Do
inputvar = InputBox(xPrompt, "Enter True or False")
msgResult = MsgBox("You have Enterd " & inputvar & " to " & xPrompt, vbYesNo + vbDefaultButton2, "Check Your Input (True/False)")
If (StrPtr(inputvar) = 0) Or (Len(inputvar) < 0) Then
MsgBox "Invalid Input - Cannot be Empty / Null / cancelled", 16, "Invalid Input."
End If
If (inputvar <> "True") And (inputvar <> "False") Then
MsgBox "Invalid Input - Enter Either True or False", 16, "Invalid Input."
End If
Loop Until (Len(inputvar) > 0) And (inputvar = "True") Or (inputvar = "False") And (msgResult = vbYes)
Loop Until (Len(inputvar) > 0) And (inputvar = "True") Or (inputvar = "False") And (msgResult = vbYes)
If inputvar = True Then
ResutlsStream.WriteLine "Step " & SSW.View.CurrentShowPosition & ". " & xPrompt & Chr(9) & ":" & Chr(9) & inputvar
Else
MsgBox "Test criteria failed, contact production engineer."
ResutlsStream.WriteLine "Step " & SSW.View.CurrentShowPosition & ". " & xPrompt & Chr(9) & ":" & Chr(9) & inputvar & " " & xvarUnit & " Failed" & " ***NCR Required***"
End If
Case "General"
xPrompt = GetINIString(SSW.View.CurrentShowPosition, "Prompt", INIPath)
Do
msgResult = 7
Do
inputvar = InputBox(xPrompt, xPrompt)
msgResult = MsgBox("You have Enterd " & inputvar & " to " & xPrompt, vbYesNo + vbDefaultButton2, "Check Input")
If (StrPtr(inputvar) = 0) Or (Len(inputvar) < 0) Then
MsgBox "Invalid Input - Cannot be Empty / Null / cancelled", 16, "Invalid Input."
End If
Loop Until (Len(inputvar) > 0) And (msgResult = vbYes)
Loop Until (Len(inputvar) > 0) And (msgResult = vbYes)
ResutlsStream.WriteLine "Step " & SSW.View.CurrentShowPosition & ". " & xPrompt & Chr(9) & ":" & Chr(9) & inputvar & " " & xvarUnit
Case "Limit"
xLimitHi = GetINIString(SSW.View.CurrentShowPosition, "LimitHi", INIPath)
xLimitLo = GetINIString(SSW.View.CurrentShowPosition, "LimitLo", INIPath)
xPrompt = GetINIString(SSW.View.CurrentShowPosition, "Prompt", INIPath)
xvarUnit = GetINIString(SSW.View.CurrentShowPosition, "varUnit", INIPath)
If GetTestCriteria(xPrompt, xLimitLo, xLimitHi, xvarUnit, result) Then
ResutlsStream.WriteLine "Step " & SSW.View.CurrentShowPosition & ". " & xPrompt & Chr(9) & ":" & Chr(9) & result & " " & xvarUnit
Else
MsgBox "Test criteria failed, contact production engineer."
Do
msgResult = 7
Do
FailedResult = InputBox("Enter Values Failed in " & xPrompt, "Enter Failed Value")
msgResult = MsgBox("You have Enterd Failed Value of " & FailedResult, vbYesNo + vbDefaultButton2, "Check Failed Input")
If (StrPtr(FailedResult) = 0) Or (Len(FailedResult) = 0) Then
MsgBox "Invalid Input - Cannot be Empty / Null / cancelled", 16, "Invalid Input."
End If
Loop Until (Len(FailedResult) > 0) And (msgResult = vbYes)
Loop Until (Len(FailedResult) > 0) And (msgResult = vbYes)
ResutlsStream.WriteLine "Step " & SSW.View.CurrentShowPosition & ". " & xPrompt & Chr(9) & ":" & Chr(9) & FailedResult & " " & xvarUnit & " Failed" & " ***NCR Required***"
End If
ResutlsStream.Close
End Select
End If
End If
If (Timed = "ON") Then
If (Len(Dir(TimeingFolder, vbDirectory)) = 0) Then
MkDir TimeingFolder
End If
If (Len(Dir(TimeingFolder & "\" & PCPver, vbDirectory)) = 0) Then
MkDir TimeingFolder & "\" & PCPver
End If
TimingFile = TimeingFolder & "\" & PCPver & "\" & "Timing-" & WorkOrder & "-" & Serial & "-" & PCPver & "-" & ToDate & ".csv"
If (Len(Dir(TimingFile)) = 0) Then
Set TimeingStream = TimeingFSO.CreateTextFile(TimingFile, True)
TimeingStream.WriteLine UserName & "'s " & ModuleName & " " & PCPver & " Build Time File"
TimeingStream.WriteLine "===================================================================================================="
TimeingStream.WriteLine "Seq/Step" & Chr(44) & "Start Time" & Chr(44) & "End Time"
Else
Set TimeingStream = TimeingFSO.OpenTextFile(TimingFile, 8)
End If
EndTime = Format(time, "hh:mm:ss")
TimeingStream.WriteLine "No:" & SSW.View.CurrentShowPosition & Chr(44) & StartTime & Chr(44) & EndTime
TimeingStream.Close
End If
End Sub
Private Function ConfirmUserInput(ByVal inputvar As Double) As Boolean
ConfirmUserInput = MsgBox("Confirm value: " & CStr(inputvar) & "?", vbYesNo + vbDefaultButton2, "Confirm value") = vbYes
End Function
Private Function IsValidUserInput(ByVal userInput As String, ByVal xLimitLo As Double, ByVal xLimitHi As Double, ByRef outResult As Double) As Boolean
Dim result As Boolean
Dim numericInput As Double
If StrPtr(userInput) = 0 Then
MsgBox "Invalid Input - Entry cannot be cancelled", 16, "Invalid User Input"
ElseIf userInput = vbNullString Then
MsgBox "Invalid Input - Entry cannot be Empty / Null", 16, "Invalid User Input"
ElseIf Not IsNumeric(userInput) Then
MsgBox "Invalid Input - Numeric Input required", 16, "Invalid User Input"
Else
numericInput = CDbl(userInput)
If numericInput < xLimitLo Or numericInput > xLimitHi Then
MsgBox "Invalid Input - Not within Limits", 16, "Invalid User Input"
Else
result = ConfirmUserInput(numericInput)
outResult = numericInput
End If
End If
IsValidUserInput = result
End Function
Private Function GetTestCriteria(ByVal xPrompt As String, ByVal xLimitLo As Double, ByVal xLimitHi As Double, ByVal xvarUnit As String, ByRef outResult As Double) As Boolean
Const failed As String = "Failed"
Dim prompt As String
prompt = "Enter Value between " & xLimitLo & xvarUnit & " and " & xLimitHi & xvarUnit & "(Inclusive)"
Dim userInput As String
Dim isValid As Boolean
Do
userInput = InputBox(prompt, xPrompt)
isValid = IsValidUserInput(userInput, xLimitLo, xLimitHi, outResult) Or userInput = failed
Loop Until isValid
GetTestCriteria = (userInput <> failed)
End Function
Private Sub TextBox1_Change()
End Sub
Private Sub TextBox2_Change()
End Sub
Private Sub TextBox2_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
End Sub
> Code in Module
Option Explicit
Option Compare Text
Private Declare Function GetPrivateProfileString Lib "kernel32" Alias "GetPrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As String, ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Long, ByVal lpFileName As String) As Long
Private Declare Function GetPrivateProfileInt Lib "kernel32" Alias "GetPrivateProfileIntA" (ByVal lpApplicationName As String, ByVal lpKeyName As String, ByVal nDefault As Long, ByVal lpFileName As String) As Long
Private Declare Function WritePrivateProfileString Lib "kernel32" Alias "WritePrivateProfileStringA" (ByVal lpApplicationName As String, ByVal lpKeyName As String, ByVal lpString As String, ByVal lpFileName As String) As Long
Private Const CONFIG_FILE = "Config.ini"
Public Function GetINIString(ByVal sApp As String, ByVal sKey As String, ByVal filepath As String) As String
Dim sBuf As String * 256
Dim lBuf As Long
lBuf = GetPrivateProfileString(sApp, sKey, "", sBuf, Len(sBuf), filepath)
GetINIString = Left$(sBuf, lBuf)
End Function
Public Function WriteINI(ByVal sApp As String, ByVal sKey As String, ByVal sValue As String) As String
WritePrivateProfileString sApp, sKey, sValue, "Config.ini"
End Function
Code in Config.ini
Config.ini to be remain in same folder as the .ppsm file
[PCPInfo]
;This will force the operator to check PCP version against BOM
;This is required as it is used to tie in the check list to the PCP
PCPver=12.3456.789.A01
;this is used as the heading for creating results files
ModuleName=NEW Validation Test Case
;this to check the correct PCP Power-point file is present with the ini file - if this is incorrect power point will not run
PCPFileName=12.3456.789.A01 NEW Validation Test Case.ppsm
[Options]
;Switch ON/OFF to collect timing data
Timed=ON
[Folders]
;If required creates last folder of the path
;folder where all check-lists/result files collected
ResultsFolder=C:\Reports\Validation
;folder where all training data collected
TrainingFolder=C:\Training Records
;folder where all timing data collected
TimeingFolder=C:\Times
;Check Who has completed training here - Not implemented
TrainedFolder=C:\TrainedOP
;Do not Use Slide No 1 - Use slide number in square brackets [x]
;First Slide collects Work Order, User name , Serial Number information
;PromptTypes Message,Date,TrueFalse,General,Limit *compulsory
;Type Message Displays Pop up message only , No Data Collection
;Type Date accepts dates in DD-MMM-YYYY format
;Type TrueFalse can be used for Passed failed, checks etc.
;Type General can be used for Part Serial numbers, batch dates
;Type Limit can be used for test parameters with a range,-
; - if not within the range "Failed" can be used to complete the step and return to a previous step
; LimitHi refers to Higher limit should be less than or equal to *compulsory for type Limit
; LimitLo Refers to Lower limit should be Greater than or equal to *compulsory for type Limit
;Prompt will pop-up the user input box wit the text as question/criteria *compulsory
;VarUnit Type of Unit Ohms,Psi,kPa etc.
[2]
PromptType=Message
LimitHi=
LimitLo=
Prompt=Revision Record
varUnit=
[4]
PromptType=Date
LimitHi=
LimitLo=
Prompt=Enter to days Date
varUnit=
[6]
PromptType=TrueFalse
LimitHi=
LimitLo=
Prompt=Enter True or False
varUnit=
[8]
PromptType=General
LimitHi=
LimitLo=
Prompt=Enter Any text
varUnit=
[10]
PromptType=Limit
LimitHi=200
LimitLo=100
Prompt=Enter Value within limits
varUnit=Bar
thanks again #retailcoder
best regards
Dumidu Roshan aka rellik - #rellik
This is more an observation than a real question: MS-Access (and VBA in general) is desperately missing a tool where error handling code can be generated automatically, and where the line number can be displayed when an error occurs. Did you find a solution? What is it? I just realized how many hundreds of hours I spared since I found the right answer to this basic problem a few years ago, and I'd like to see what are your ideas and solutions on this very important issue.
What about using "Erl", it will display the last label before the error (e.g., 10, 20, or 30)?
Private Sub mySUB()
On Error GoTo Err_mySUB
10:
Dim stDocName As String
Dim stLinkCriteria As String
20:
stDocName = "MyDoc"
30:
DoCmd.openform stDocName, acFormDS, , stLinkCriteria
Exit_mySUB:
Exit Sub
Err_mySUB:
MsgBox Err.Number & ": " & Err.Description & " (" & Erl & ")"
Resume Exit_mySUB
End Sub
My solution is the following:
install MZ-Tools, a very interesting add-on for VBA. No they did not pay me to write this. Version 3 was free, but since version 8.0, the add-in is commercially sold.
program a standard error handler code such as this one (see MZ-Tools menu/Options/Error handler):
On Error GoTo {PROCEDURE_NAME}_Error
{PROCEDURE_BODY}
On Error GoTo 0
Exit {PROCEDURE_TYPE}
{PROCEDURE_NAME}_Error:
debug.print "#" & Err.Number, Err.description, "l#" & erl, "{PROCEDURE_NAME}", "{MODULE_NAME}"
This standard error code can be then automatically added to all of your procs and function by clicking on the corresponding button in the MZ-Tools menu. You'll notice that we refer here to a hidden and undocumented function in the VBA standard library, 'Erl', which stands for 'error line'. You got it! If you ask MZ-Tools to automatically number your lines of code, 'Erl' will then give you the number of the line where the error occured. You will have a complete description of the error in your immediate window, such as:
#91, Object variable or With block variable not set, l# 30, addNewField, Utilities
Of course, once you realize the interest of the system, you can think of a more sophisticated error handler, that will not only display the data in the debug window but will also:
display it as a message on the screen
Automatically insert a line in an error log file with the description of the error or
if you are working with Access or if you are connected to a database, automatically add a record to a Tbl_Error table!
meaning that each error generated at the user level can be stored either in a file or a table, somewhere on the machine or the network. Are we talking about building an automated error reporting system working with VBA?
Well there are a couple of tools that will do what you ask MZ Tools and FMS Inc come to mind.
Basically they involve adding an:
On Error GoTo ErrorHandler
to the top of each proc
and at the end they put an:
ErrorHandler:
Call MyErrorhandler Err.Number, Err.Description, Err.LineNumber
label with usually a call to a global error handler where you can display and log custom error messages
You can always roll your own tool like Chip Pearson did. VBA can actually access it's own IDE via the Microsoft Visual Basic for Applications Extensibility 5.3 Library. I've written a few class modules that make it easier to work with myself. They can be found on Code Review SE.
I use it to insert On Error GoTo ErrHandler statements and the appropriate labels and constants related to my error handling schema. I also use it to sync up the constants with the actual procedure names (if the function names should happen to change).
There is no need to buy tools DJ mentioned. Here is my code for free:
Public Sub InsertErrHandling(modName As String)
Dim Component As Object
Dim Name As String
Dim Kind As Long
Dim FirstLine As Long
Dim ProcLinesCount As Long
Dim Declaration As String
Dim ProcedureType As String
Dim Index As Long, i As Long
Dim LastLine As Long
Dim StartLines As Collection, LastLines As Collection, ProcNames As Collection, ProcedureTypes As Collection
Dim gotoErr As Boolean
Kind = 0
Set StartLines = New Collection
Set LastLines = New Collection
Set ProcNames = New Collection
Set ProcedureTypes = New Collection
Set Component = Application.VBE.ActiveVBProject.VBComponents(modName)
With Component.CodeModule
' Remove empty lines on the end of the code
For i = .CountOfLines To 1 Step -1
If Component.CodeModule.Lines(i, 1) = "" Then
Component.CodeModule.DeleteLines i, 1
Else
Exit For
End If
Next i
Index = .CountOfDeclarationLines + 1
Do While Index < .CountOfLines
gotoErr = False
Name = .ProcOfLine(Index, Kind)
FirstLine = .ProcBodyLine(Name, Kind)
ProcLinesCount = .ProcCountLines(Name, Kind)
Declaration = Trim(.Lines(FirstLine, 1))
LastLine = FirstLine + ProcLinesCount - 2
If InStr(1, Declaration, "Function ", vbBinaryCompare) > 0 Then
ProcedureType = "Function"
Else
ProcedureType = "Sub"
End If
Debug.Print Component.Name & "." & Name, "First: " & FirstLine, "Lines:" & ProcLinesCount, "Last: " & LastLine, Declaration
Debug.Print "Declaration: " & Component.CodeModule.Lines(FirstLine, 1), FirstLine
Debug.Print "Closing Proc: " & Component.CodeModule.Lines(LastLine, 1), LastLine
' do not insert error handling if there is one already:
For i = FirstLine To LastLine Step 1
If Component.CodeModule.Lines(i, 1) Like "*On Error*" Then
gotoErr = True
Exit For
End If
Next i
If Not gotoErr Then
StartLines.Add FirstLine
LastLines.Add LastLine
ProcNames.Add Name
ProcedureTypes.Add ProcedureType
End If
Index = FirstLine + ProcLinesCount + 1
Loop
For i = LastLines.Count To 1 Step -1
If Not (Component.CodeModule.Lines(StartLines.Item(i) + 1, 1) Like "*On Error GoTo *") Then
Component.CodeModule.InsertLines LastLines.Item(i), "ExitProc_:"
Component.CodeModule.InsertLines LastLines.Item(i) + 1, " Exit " & ProcedureTypes.Item(i)
Component.CodeModule.InsertLines LastLines.Item(i) + 2, "ErrHandler_:"
Component.CodeModule.InsertLines LastLines.Item(i) + 3, " Call LogError(Err, Me.Name, """ & ProcNames.Item(i) & """)"
Component.CodeModule.InsertLines LastLines.Item(i) + 4, " Resume ExitProc_"
Component.CodeModule.InsertLines LastLines.Item(i) + 5, " Resume ' use for debugging"
Component.CodeModule.InsertLines StartLines.Item(i) + 1, " On Error GoTo ErrHandler_"
End If
Next i
End With
End Sub
Put it in a module and call it from Immediate Window every time you add new function or sub to a form or module like this (Form1 is name of your form):
MyModule.InsertErrHandling "Form_Form1"
It will alter your ode in Form1 from this:
Private Function CloseIt()
DoCmd.Close acForm, Me.Name
End Function
to this:
Private Function CloseIt()
On Error GoTo ErrHandler_
DoCmd.Close acForm, Me.Name
ExitProc_:
Exit Function
ErrHandler_:
Call LogError(Err, Me.Name, "CloseIt")
Resume ExitProc_
Resume ' use for debugging
End Function
Create now in a module a Sub which will display the error dialog and where you can add inserting the error to a text file or database:
Public Sub LogError(ByVal objError As ErrObject, moduleName As String, Optional procName As String = "")
On Error GoTo ErrHandler_
Dim sql As String
MsgBox "Error " & Err.Number & " Module " & moduleName & Switch(procName <> "", " in " & procName) & vbCrLf & " (" & Err.Description & ") ", vbCritical
Exit_:
Exit Sub
ErrHandler_:
MsgBox "Error in LogError procedure " & Err.Number & ", " & Err.Description
Resume Exit_
Resume ' use for debugging
End Sub
This code does not enter error handling if there is already "On Error" statement in a proc.
Love it Vlado!
I realize this is an old post, but I grabbed it and gave it a try, but I ran into a number of issues with it, which I managed to fix. Here's the code with fixes:
First of course, be sure to add the "Microsoft Visual Basic for Applications Extensibility 5.3" library to your project, and add these subroutines / modules to your project as well.
First, the module with the main code was named "modVBAChecks", and contained the following two subroutines:
To go through all modules (behind forms, sheets, the workbook, and classes as well, though not ActiveX Designers):
Sub AddErrorHandlingToAllProcs()
Dim VBProj As VBIDE.VBProject
Dim VBComp As VBIDE.VBComponent
Dim lCtr As Long
StartNewWorksheetLog
Set VBProj = Workbooks("LabViewAnalysisTools.xla").VBProject
For Each VBComp In VBProj.VBComponents
If VBComp.Type <> vbext_ct_ActiveXDesigner Then
If VBComp.Name <> "modVBAChecks" And VBComp.Name <> "modLogToWorksheet" Then
AddToWksLog "============ Looking at Module """ & VBComp.Name & """"
'InsertErrHandling VBComp.Name
AddToWksLog
AddToWksLog
End If
End If
Next
MsgBox "Done!", vbSystemModal
End Sub
Then the modified version of your code (including a suggested change by
Rafał B.):
Public Sub InsertErrHandling(modsProcName As String)
' Modified from code submitted to StackOverflow by user Vlado, originally found
' here: https://stackoverflow.com/questions/357822/automatically-generating-handling-of-issues
Dim vbcmA As VBIDE.CodeModule
Dim ProcKind As VBIDE.vbext_ProcKind
Dim LineProcKind As VBIDE.vbext_ProcKind
Dim sProcName As String
Dim sLineProcName As String
Dim lFirstLine As Long
Dim lProcLinesCount As Long
Dim lLastLine As Long
Dim sDeclaration As String
Dim sProcType As String
Dim lLine As Long, lLine2 As Long
Dim sLine As String
Dim lcStartLines As Collection, lcLastlines As Collection, scProcsProcNames As Collection, scProcTypes As Collection
Dim bAddHandler As Boolean
Dim lLinesAbove As Long
Set lcStartLines = New Collection
Set lcLastlines = New Collection
Set scProcsProcNames = New Collection
Set scProcTypes = New Collection
Set vbcmA = Application.VBE.ActiveVBProject.VBComponents(modsProcName).CodeModule
' Remove empty lines on the end of the module. Cleanup, not error handling.
lLine = vbcmA.CountOfLines
If lLine = 0 Then Exit Sub ' Nothing to do!
Do
If Trim(vbcmA.Lines(lLine, 1)) <> "" Then Exit Do
vbcmA.DeleteLines lLine, 1
lLine = lLine - 1
Loop
lLine = vbcmA.CountOfDeclarationLines + 1
Do While lLine < vbcmA.CountOfLines
bAddHandler = False
' NOTE: ProcKind is RETRUNED from ProcOfLine!
sProcName = vbcmA.ProcOfLine(lLine, ProcKind)
' Fortunately ProcBodyLine ALWAYS returns the first line of the procedure declaration!
lFirstLine = vbcmA.ProcBodyLine(sProcName, ProcKind)
sDeclaration = Trim(vbcmA.Lines(lFirstLine, 1))
Select Case ProcKind
Case VBIDE.vbext_ProcKind.vbext_pk_Proc
If sDeclaration Like "*Function *" Then
sProcType = "Function"
ElseIf sDeclaration Like "*Sub *" Then
sProcType = "Sub"
End If
Case VBIDE.vbext_ProcKind.vbext_pk_Get, VBIDE.vbext_ProcKind.vbext_pk_Let, VBIDE.vbext_ProcKind.vbext_pk_Set
sProcType = "Property"
End Select
' The "lProcLinesCount" function will sometimes return ROWS ABOVE
' the procedure, possibly up until the prior procedure,
' and often rows BELOW the procedure as well!!!
lProcLinesCount = vbcmA.ProcCountLines(sProcName, ProcKind)
lLinesAbove = 0
lLine2 = lFirstLine - 1
If lLine2 > 0 Then
Do
sLineProcName = vbcmA.ProcOfLine(lLine2, LineProcKind)
If Not (sLineProcName = sProcName And LineProcKind = ProcKind) Then Exit Do
lLinesAbove = lLinesAbove + 1
lLine2 = lLine2 - 1
If lLine2 = 0 Then Exit Do
Loop
End If
lLastLine = lFirstLine + lProcLinesCount - lLinesAbove - 1
' Now need to trim off any follower lines!
Do
sLine = Trim(vbcmA.Lines(lLastLine, 1))
If sLine = "End " & sProcType Or sLine Like "End " & sProcType & " '*" Then Exit Do
lLastLine = lLastLine - 1
Loop
AddToWksLog modsProcName & "." & sProcName, "First: " & lFirstLine, "Lines:" & lProcLinesCount, "Last: " & lLastLine
AddToWksLog "sDeclaration: " & vbcmA.Lines(lFirstLine, 1), lFirstLine
AddToWksLog "Closing Proc: " & vbcmA.Lines(lLastLine, 1), lLastLine
If lLastLine - lFirstLine < 8 Then
AddToWksLog " --------------- Too Short to bother!"
Else
bAddHandler = True
' do not insert error handling if there is one already:
For lLine2 = lFirstLine To lLastLine Step 1
If vbcmA.Lines(lLine2, 1) Like "*On Error GoTo *" And Not vbcmA.Lines(lLine2, 1) Like "*On Error GoTo 0" Then
bAddHandler = False
Exit For
End If
Next lLine2
If bAddHandler Then
lcStartLines.Add lFirstLine
lcLastlines.Add lLastLine
scProcsProcNames.Add sProcName
scProcTypes.Add sProcType
End If
End If
AddToWksLog
lLine = lFirstLine + lProcLinesCount + 1
Loop
For lLine = lcLastlines.Count To 1 Step -1
vbcmA.InsertLines lcLastlines.Item(lLine), "ExitProc:"
vbcmA.InsertLines lcLastlines.Item(lLine) + 1, " Exit " & scProcTypes.Item(lLine)
vbcmA.InsertLines lcLastlines.Item(lLine) + 2, "ErrHandler:"
vbcmA.InsertLines lcLastlines.Item(lLine) + 3, " ShowErrorMsg Err, """ & scProcsProcNames.Item(lLine) & """, """ & modsProcName & """"
vbcmA.InsertLines lcLastlines.Item(lLine) + 4, " Resume ExitProc"
' Now replace any "On Error Goto 0" lines with "IF ErrorTrapping Then On Error Goto ErrHandler"
For lLine2 = lcStartLines(lLine) To lcLastlines(lLine)
sLine = vbcmA.Lines(lLine2, 1)
If sLine Like "On Error GoTo 0" Then
vbcmA.ReplaceLine lLine2, Replace(sLine, "On Error Goto 0", "IF ErrorTrapping Then On Error Goto ErrHandler")
End If
Next
lLine2 = lcStartLines.Item(lLine)
Do
sLine = vbcmA.Lines(lLine2, 1)
If Not sLine Like "* _" Then Exit Do
lLine2 = lLine2 + 1
Loop
vbcmA.InsertLines lLine2 + 1, " If ErrorTrapping Then On Error GoTo ErrHandler"
Next lLine
End Sub
And rather than pushing things to the Immediate window I used subroutines in a module I named "modLogToWorksheet", the full module being here:
Option Explicit
Private wksLog As Worksheet
Private lRow As Long
Public Sub StartNewWorksheetLog()
Dim bNewSheet As Boolean
bNewSheet = True
If ActiveSheet.Type = xlWorksheet Then
Set wksLog = ActiveSheet
bNewSheet = Not (wksLog.UsedRange.Cells.Count = 1 And wksLog.Range("A1").Formula = "")
End If
If bNewSheet Then Set wksLog = ActiveWorkbook.Worksheets.Add
lRow = 1
End Sub
Public Sub AddToWksLog(ParamArray sMsg() As Variant)
Dim lCol As Long
If wksLog Is Nothing Or lRow = 0 Then StartNewWorksheetLog
If Not (IsNull(sMsg)) Then
For lCol = 0 To UBound(sMsg)
If sMsg(lCol) <> "" Then wksLog.Cells(lRow, lCol + 1).Value = "'" & sMsg(lCol)
Next
End If
lRow = lRow + 1
End Sub
And finally, here's my Error Dialog generator:
Public Sub ShowErrorMsg(errThis As ErrObject, strSubName As String, strModName As String _
, Optional vbMBStyle As VbMsgBoxStyle = vbCritical, Optional sTitle As String = APP_TITLE)
If errThis.Number <> 0 Then
MsgBox "An Error Has Occurred in the Add-in. Please inform " & ADMINS & " of this problem." _
& vbCrLf & vbCrLf _
& "Error #: " & errThis.Number & vbCrLf _
& "Description: " & " " & errThis.Description & vbCrLf _
& "Subroutine: " & " " & strSubName & vbCrLf _
& "Module: " & " " & strModName & vbCrLf _
& "Source: " & " " & errThis.Source & vbCrLf & vbCrLf _
& "Click OK to continue.", vbMBStyle Or vbSystemModal, sTitle
End If
End Sub
Hope future users find it useful!