How to make my VBA code look better and reduce redundancy? - vba

I have written very ugly VBA code, but I don't know how I can make this look better.
I have a function (say Function Main) that calls another 3 functions inside it. These 3 functions have to happen in order, and if one fails, the rest shouldn't happen. Also, I need to retrieve proper, descriptive error message. I have another software that invokes VBA code and calls this Main function, and if there is an error, I need to use that information, so I am returning a value (either a descriptive error message or "Completed") from Main as well.
Function Main
Dim result As String
result = ProcessA();
If result <> "OK" Then
Main = result
Exit Function
End If
result = ProcessB();
If result <> "OK" Then
Main = result
Exit Function
End If
result = ProcessC();
If result <> "OK" Then
Main = result
Exit Function
End If
Main = "Completed"
End Function
Function ProcessA()
On Error GoTo Errored
'Do some operation
ProcessA = "OK" 'At the end of operation, assign OK and
Exit Function 'Exit function here
Errored: 'In case of an error, get error information and return it
ProcessA = "Error Line: " & Erl & Chr(13) _
& "Error Description: " & Err.Description & Chr(13) _
& "Error at Process A"
End Function
'ProcessB and ProcessC also have a very similar style.
As you can see, my Main function has so many redundant code, but I can't think of any other better way. Could someone give me advice on how to make this better?

I would suggest re-throwing the error then catching it in the calling method:
Function Main()
On Error GoTo haveError
ProcessA
ProcessB
ProcessC
Main = "Completed"
Exit Function
haveError:
Main = err.Description
End Function
Sub ProcessA()
On Error GoTo Errored
'Do some operation
Exit Sub
Errored:
err.Raise Number:=err.Number, Description:=DescribeError(err, "ProcessA")
End Sub
Sub ProcessB()
On Error GoTo Errored
'Do some operation
Debug.Print 1 / 0 'eg. error here....
Exit Sub
Errored:
err.Raise Number:=err.Number, Description:=DescribeError(err, "ProcessB")
End Sub
Sub ProcessC()
On Error GoTo Errored
'Do some operation
Exit Sub
Errored:
err.Raise Number:=err.Number, Description:=DescribeError(err, "ProcessC")
End Sub
'utility
Function DescribeError(err As ErrObject, procName As String)
DescribeError = "Error in method: " & procName & vbLf & _
"Error Line: " & erl & vbLf & _
"Error Description: " & err.Description
End Function

Function Main
Dim result As String
result = ProcessA(): If result <> "OK" Then goto errResult
result = ProcessB(): If result <> "OK" Then goto errResult
result = ProcessC(): If result <> "OK" Then goto errResult
Main = "Completed"
Exit Function
errResult:
Main=result
End Function

Private errorMsg As String
Function Main()
If ProcessA Then
If ProcessB Then
If ProcessC Then
Main = "Completed"
End If
End If
End If
If Main = vbNullString Then Main = errorMsg
End Function
Private Function ProcessA() As Boolean
ProcessA = True
Exit Function
Err:
ReportError "Process A"
End Function
Private Sub ReportError(argSrc As String)
errorMsg = "Error Line: " & Erl & Chr(13) _
& "Error Description: " & Err.Description & Chr(13) _
& "Error at " & argSrc
End Sub

Using Application.Run(), you can process any number of steps in a loop:
Function Main()
Dim f, result
For Each f In Array("ProcessA", "ProcessB", "ProcessC") ' and so on
result = Application.Run(f)
If result <> "OK" Then
Main = result
Exit Function
End If
Next
Main = "Completed"
End Function

Related

How to check if a filter has any results?

I apply multiple filters (defined in an array), run through them and export pdfs.
When a filter has no value the code stops because there is nothing to export.
I am using On Error Resume Next but this is not sustainable. If the error happens twice it breaks again.
How can I check if the filter has any results?
For i = 1 To 18
FilterApply name:=FL(i)
names = ActiveProject.ProjectSummaryTask.name & " " & FL(i)
DocumentExport FileName:="C:\temp\" & names, FromDate:="01/07/22 6:00", ToDate:="15/07/22 18:00", FileType:=pjPDF
Next
I found the answer. quite easy actually!
**SelectTaskField Row:=0, Column:="name"
If ActiveCell.Text <> "" Then**
For i = 1 To 18
FilterApply name:=FL(i)
SelectTaskField Row:=0, Column:="name"
If ActiveCell.Text <> "" Then
If Left(FL(i), 1) = "_" Then ' create file name
names = ActiveProject.ProjectSummaryTask.name & " " & Mid(FL(i), 2, Len(FL(i)))
Else
names = ActiveProject.ProjectSummaryTask.name & " " & FL(i)
End If
DocumentExport FileName:="C:\temp\" & names, FromDate:="01/07/22 6:00", ToDate:="15/07/22 18:00", FileType:=pjPDF
End If
Next
I've answered a similar question on here before. I recommend creating a separate function to check if the Filter is empty, like this:
Public Function CurrentFilterHasTasks() As Boolean
Dim result As Boolean
On Error GoTo ErrHandler
Application.SelectAll 'select everything in the current filter
'Application.ActiveSelection.Tasks will fail if there are only blank rows in the active selection
If Application.ActiveSelection.Tasks.Count > 0 Then
result = True
End If
CurrentFilterHasTasks = result
'call exit function here so the code below the error handler does not run
Exit Function
ErrHandler:
result = False
CurrentFilterHasTasks = result
End Function
Then you can call the method in your code:
For i = 1 To 18
FilterApply name:=FL(i)
If CurrentFilterHasTasks Then
names = ActiveProject.ProjectSummaryTask.name & " " & FL(i)
DocumentExport FileName:="C:\temp\" & names, FromDate:="01/07/22 6:00", ToDate:="15/07/22 18:00", FileType:=pjPDF
End If
Next

Method ‘FindFirst’ of object ‘Recordset2’ failed. After not saving new record

In a form, create a new record, edit some data but before saving it use a combo box on the form to select another record to navigate to. This triggers the cboSalePicker_AfterUpdate. Then during this sub Form_BeforeUpdate executes. The user clicks no on the MsgBox to not save the new record. Then the rest of cboSalePicker_AfterUpdate is executed however the following error message is displayed:
Error Message
Error number -2147417848: Method ‘FindFirst’ of object ‘Recordset2’ failed.
Associated with the line Me.Recordset.FindFirst "[SaleID] = " & Str(Nz(cboSalePicker.Value, 0))
However, if the new record is saved no error is produced and the code performs as expected.
Form_BeforeUpdate
Private Sub Form_BeforeUpdate(Cancel As Integer)
On Error GoTo ErrorHandler
Dim strMsg As String
Dim iResponse As Integer
'Specify the mesage to display
strMsg = "Do you wish to save the changes?" & Chr(10)
strMsg = strMsg & "Click Yes to Save or No to Discard changes."
'Display the msg box
iResponse = MsgBox(strMsg, vbQuestion + vbYesNo, "Save Record?")
'Check response
If iResponse = vbNo Then
'Undo the change.
DoCmd.RunCommand acCmdUndo
'Cancel the update
Cancel = True
End If
Exit Sub
ErrorHandler:
MsgBox "Error number " & Err.Number & ": " & Err.Description
End Sub
cboSalePicker_AfterUpdate
Private Sub cboSalePicker_AfterUpdate()
On Error GoTo ErrorHandler
Me.Recordset.FindFirst "[SaleID] = " & Str(Nz(cboSalePicker.Value, 0))
Exit Sub
ErrorHandler:
MsgBox "Error number " & Err.Number & ": " & Err.Description
End Sub
Thanks
You are converting Your SaleID into a String using this
Str(Nz(cboSalePicker.Value, 0))
But your find first is looking for a number. If SaleID is a number then remove the Str() function from your code around the combobox value.
To show the concatenation try this
Private Sub cboSalePicker_AfterUpdate()
On Error GoTo ErrorHandler
Dim sCriteria as String
sCriteria = "[SaleID] = " & Nz(Me.cboSalePicker, 0)
debug.print sCriteria
Me.Recordset.FindFirst sCriteria
Exit Sub
ErrorHandler:
MsgBox "Error number " & Err.Number & ": " & Err.Description
End Sub
Comment out the first error handler line whilst you are debugging things.

VBA Excel Stop On Error Goto

I have written On Error GoTo ErrorMessage somewhere in a subroutine. I want to stop this command after End if command as shown below.
On Error GoTo ErrorMessage
Sheet2.Range("A1").Font.Bold = True
Sheet2.Range("B1").Font.Bold = True
If LastRow_Sh2 >= First_Row_Sheet2 Then
Sheet2.Range(FromCol & First_Row_Sheet2 & ":" & ToCol & LastRow_Sh2).ClearContents
Exit Sub
End If
' Stop here
' I have some codes here
ErrorMessage:
MsgBox ("Error message: The input values are invalid")
Take a look at the example below that shows both - error handling manually and allow VBA to catch a runtime error:
Sub Test()
On Error GoTo ErrorHandler
Dim Divisor As Integer
Dim Num As Integer
Dim Answer As Double
Num = 100
Divisor = 0
' ================================
' throw an error here forcefully
' and allow ErrorHandler to handle
' the error
' ================================
Answer = Num / Divisor
MsgBox "Answer is " & Answer, vbOKOnly + vbInformation, "Answer"
' stop error handling
On Error GoTo 0
' ================================
' throw an error here forcefully
' and allow VBA to handle the error
' ================================
Answer = Num / Divisor
MsgBox "Answer is " & Answer, vbOKOnly + vbInformation, "Answer"
Exit Sub
ErrorHandler:
MsgBox "Handling the error here", vbOKOnly + vbInformation, "ErrorHandler"
Resume Next
End Sub
Based on this, you can modify your code slightly to allow VBA to handle the error on runtime.
On Error GoTo ErrorMessage
Sheet2.Range("A1").Font.Bold = True
Sheet2.Range("B1").Font.Bold = True
If LastRow_Sh2 >= First_Row_Sheet2 Then
Sheet2.Range(FromCol & First_Row_Sheet2 & ":" & ToCol & LastRow_Sh2).ClearContents
Exit Sub
End If
' Stop here
' The statement below will disable error handling that was
' done by ErrorMessage
On Error GoTo 0
' I have some codes here
' If this block of code has errors, VBA will handle it and
' allow debugging
ErrorMessage:
MsgBox ("Error message: The input values are invalid")
try this
On Error Resume Next '".. GoTo ErrorMessage" replaced by "...resume next"
Sheet2.Range("A1").Font.Bold = True
Sheet2.Range("B1").Font.Bold = True
If LastRow_Sh2 >= First_Row_Sheet2 Then
Sheet2.Range(FromCol & First_Row_Sheet2 & ":" & ToCol & LastRow_Sh2).ClearContents
Exit Sub
End If
If Err.Number > 0 Then GoTo ErrorMessage ' Stop here
' I have some codes here
ErrorMessage:
MsgBox ("Error message: The input values are invalid")

vba error number 9 subscript out of range

I'm trying to make a Excel 2007 vba code to select a sheet using userform. While testing, I'm getting 'Subscript out of range' Error if I input a sheetname which is not there in workbook. my code is below.
Private Sub Okbtn_Click()
sheetname = Me.ComboBox1.Value
If sheetname = "" Then
Unload Me
MsgBox "Sheet Name not enetered", vbExclamation, "Hey!"
Exit Sub
End If
ThisWorkbook.Sheets(sheetname).Activate 'Error points here!!
On Error GoTo errmsg
'If Error.Value = 9 Then GoTo errmsg
Unload Me
MsgBox "Now you are in sheet: " & sheetname & "!", vbInformation, "Sheet Changed"
errmsg:
MsgBox ("Sheet name not found in " & ThisWorkbook.Name & " !")
End Sub
The error is in ThisWorkbook.Sheets(sheetname).Activate . I tried to put some error handing tricks before and after the problem line, but Im getting the same error-9.
Im very new to coding. I think I explained the problem correctly. I want to avoid the error from popping up, but should show a customized message instead.
If you move your On Error GoTo errmsg code line above the worksheet activation, the error should be handled by the error trap routine. You just need to exit the sub before reaching the same routine if successful.
On Error GoTo errmsg
ThisWorkbook.Sheets(sheetname).Activate 'Error points here!!
Unload Me
MsgBox "Now you are in sheet: " & sheetname & "!", vbInformation, "Sheet Changed"
Exit Sub
errmsg:
MsgBox ("Sheet name not found in " & ThisWorkbook.Name & " !")
End Sub
You need to set the error handler before executing the instruction that might cause an error. Something like this
Private Sub Okbtn_Click()
sheetname = Me.ComboBox1.Value
If sheetname = "" Then
Unload Me
MsgBox "Sheet Name not enetered", vbExclamation, "Hey!"
Exit Sub
End If
On Error GoTo errmsg
ThisWorkbook.Sheets(sheetname).Activate 'Error points here!!
'If Error.Value = 9 Then GoTo errmsg
Unload Me
MsgBox "Now you are in sheet: " & sheetname & "!", vbInformation, "Sheet Changed"
Exit Sub ' Avoid executing handler code when ther is no error
errmsg:
MsgBox ("Sheet name not found in " & ThisWorkbook.Name & " !")
End Sub
put On Error GoTo errmsg above ThisWorkbook.Sheets(sheetname).Activate
'''''
On Error GoTo errmsg
ThisWorkbook.Sheets(sheetname).Activate
'''''
error handling always must be before the line where you can receive the error
It would be clearer if you error handling was wrapped around a single line testing if the worksheet existed
As an example your current code will flag any error - such as the sheet being hidden - as the sheet not existing.
Private Sub Okbtn_Click()
Dim strSht As String
Dim ws As Worksheet
strSht = Me.ComboBox1.Value
If Len(strSht) = 0 Then
Unload Me
MsgBox "Sheet Name not entered", vbExclamation, "Hey!"
Exit Sub
End If
On Error Resume Next
Set ws = ThisWorkbook.Sheets(strSht)
On Error GoTo 0
If Not ws Is Nothing Then
If ws.Visible Then
Application.Goto ws.[a1]
MsgBox "Now you are in sheet: " & strSht & "!", vbInformation, "Sheet Changed"
Else
MsgBox ("Sheet exists but is hidden")
End If
Else
MsgBox ("Sheet name not found in " & ThisWorkbook.Name & " !")
End If
End Sub

VBA - On Error GoTo ErrHandler:

I have a simple question about error-handling in VBA.
I know how to use the On Error GoTo ErrHandler statement but instead using my own code at the specified label, I would rather use a prefabricated VBA-message. Something like this in C#:
catch(Exception ex){
Console.Writeline(ex.Message);
}
Create an ErrorHandler Module and place this sub in it.
Public Sub messageBox(moduleName As String, procName As String, Optional style As VbMsgBoxStyle = vbCritical)
MsgBox "Module: " & moduleName & vbCrLf & _
"Procedure: " & procName & vbCrLf & _
Err.Description, _
style, _
"Runtime Error: " & Err.number
End Sub
Call it from anywhere in your project like so.
Private sub Foo()
On Error GoTo ErrHandler
'do stuff
ExitSub:
' clean up before exiting
Exit Sub
ErrHandler:
ErrorHandler.messageBox "ThisModuleName","Foo"
Resume ExitSub
End Sub
I use a module scoped constant to hold the module name.
Modify to suit your needs.
In your error handler code your can access the Err.Number and Err.Description. The Description in the error message you would have seen without error handling, so is the equivalent of ex.Message in your code sample.