Question #1: Does Public myGobalVar As Integer need to be re-defined when a run-time error occurs?
Question #2: How do I automatically re-initialize my gobal variables whenever a run-time error occurs in the code?
Question #3: Would this error handling routine fail when dealing with unforeseen scenarios? How do I make it more fool-proof or robust?
Please refer to the pseudo code below.
''' Question #1: Does Public myGobalVar As Integer need to be re-defined when a run-time error occurs?
Public myGobalVar As Integer
Sub Application_Startup()
Call InitializeGobalVar()
End Sub
Sub InitializeGobalVar()
myGobalVar = 3
End Sub
Sub One_in_many_mySubs()
On Error Goto ErrHandler_myGobalVar
Dim objApp as Outlook.Application
Set objApp as Application
msgbox myGlobalVar
' erroneous event may occur somewhere in the code
Err.Clear
' skip ErrHandler_myGobalVar and exit sub after set objApp = Nothing
ErrHandler_myGobalVar:
if Err.number <> 0 then
''' Question #2: How do I automatically restart InitializeGobalVar whenever a run-time error occurs in the code?
Call InitializeGobalVar
MsgBox Err.Description
end if
Set objApp = Nothing
End Sub
Related
My form allows users to filter by various controls, including a search box for strings. A separate function, CalculateSearchString, processes this search field for filtering (keywords, exact phrases etc); and within this function I use error handling to trap errors caused by incorrect user input (i.e. mucking up the punctuation). The error handling works without a hitch, but I would like the code to come to a complete stop if the search input is incorrect.
What I want:
user inputs incorrectly formatted search string, clicks on the filter
function CalculateSearchFilter throws error. Message box: "fix your search terms!"
code stops completely, and filter is not applied
What actually happens:
user inputs incorrectly formatted search string, clicks on the filter
function CalculateSearchFilter throws error. Message box: "fix your search terms!" Exit function
calling procedure cmdFilterOn still runs, applying an incomplete filter (as though the search box had been empty)
Question: How do I halt code execution completely, not just in the function but in its calling procedure(s)? The function is used in more than one place, so merging it with the calling procedure is not practical.
Private Sub cmdFilterOn()
Dim strSearch As String
strSearch = CalculateSearchFilter
'do more stuff
End Sub
Private Function CalculateSearchFilter() As String
On Error GoTo ErrHandler
'do stuff
If 'user input is wrong, then raise a custom error:
Err.Raise 50000
End If
ExitHandler:
Exit Function
ErrHandler:
If Err.Number = 50000 Then msgbox "Fix your search terms!"
Resume ExitHandler
End Sub
Private Sub cmdYetAnotherButton()
'which also calls on CalculateSearchFilter
End Sub
Moving the input validation to the calling procedure isn't practical either, as it would force me to repeat much of the code in CalculateSearchFilter().
(As I was writing this another solution occurred to me, which is to set CalculateSearchFilter = "an error occurred" in the function error handler, and in the calling procedure
If CalculateSearchFilter = "an error occurred" Then Exit Sub
...but is there a more "official" answer?)
You could try to return a boolean value instead
Option Explicit
Private Sub cmdFilterOn()
Dim strSearch As String
If CalculateSearchFilter(strSearch) Then
'do more stuff
End If
End Sub
Private Function CalculateSearchFilter(ByRef strSearch As String) As Boolean
On Error GoTo ErrHandler
'do stuff
If 'user input is wrong, then raise a custom error:
Err.Raise 50000
End If
CalculateSearchFilter = True
ExitHandler:
Exit Function
ErrHandler:
If Err.Number = 50000 Then MsgBox "Fix your search terms!"
CalculateSearchFilter = False
Resume ExitHandler
End Function
Private Sub cmdYetAnotherButton()
'which also calls on CalculateSearchFilter
End Sub
Alternative:
As mentioned in the comments if you would like to get a string back then I would return an empty string. I would not recommend to return a string like an error occured or whatsoever
Option Explicit
Private Sub cmdFilterOn()
Dim strSearch As String
strSearch = CalculateSearchFilter
If Len(strSearch) > 0 Then
'do more stuff
End If
End Sub
Private Function CalculateSearchFilter() As String
On Error GoTo ErrHandler
'do stuff
If 'user input is wrong, then raise a custom error:
Err.Raise 50000
End If
ExitHandler:
Exit Function
ErrHandler:
If Err.Number = 50000 Then MsgBox "Fix your search terms!"
CalculateSearchFilter = vbNullString
Resume ExitHandler
End Function
Private Sub cmdYetAnotherButton()
'which also calls on CalculateSearchFilter
End Sub
I think I'm misunderstanding something simple here, as usual, some fundamental principle I have yet to learn.
Anyway, I am trying to set up a combobox for the purpose of selecting from the active workbooks and passing the workbook name to a variable for use in future code.
However, I could only get this to work by setting said variable type as Variant, and after the code finishes executing I am still getting an error... so what's going on there then?
Private Sub cmdPopulate_Click()
If cmbWorkbooks.Value <> "" Then
selectedWorkbook = cmbWorkbooks.Value
Call populateChecklist
End If
Unload Me
End Sub
Private Sub UserForm_Initialize()
For Each wb In Workbooks
cmbWorkbooks.AddItem wb.Name
Next wb
Me.Show
End Sub
Public wb As Workbook
Public ws As Worksheet
Public selectedWorkbook As Variant
Sub populateChecklist()
MsgBox "hello " & selectedWorkbook
End Sub
Edit:
To clarify the error I receive upon finished execution is:
Run-time error '91':
Object variable or With block variable not set
Edit2:
ok I think I've figured it out...
I commented out everything by degrees until I found the culprit.
It seems to be caused by Me.Show in the userform_Initialize sub, which is apparently redundant code anyway!
Some background:
Precursor: I have looked around SO at the other Error Handling questions, but I haven't been able to fully apply the answers to my situation. I feel like Err.Raise is how I would accomplish what I'll describe below. But, I'm not sure how to implement it in the way I need. If I were to use Err.Raise how would I exit the Sub1-15 first before raising the error code in the main sub?
That being said,
I have a large Excel VBA project that performs a plethora of different routines. I chose to call all routines from one main routine for means of later maintenance on the individual routines. I have an On Error handler in my main sub that I would like to have triggered if an error is thrown in any of the routines called from that main routine.
Is there a way to:
Record the
Error type that occurred
Error message
Sub that raises the error
On Error exit that sub to return to the main sub, then
Raise the error that just occurred in the other sub so that the NotifyandRepair Error Handler is called?
I have the following situation
Sub MainSub()
On Error GoTo NotifyandCorrect
Call Sub1
Call Sub2
...
Call Sub15
Exit Sub
NotifyandCorrect:
'Send copy of faulty file, the error code and Sub that caused it
'Then stop macro execution completely
End Sub
Sub Sub1()
On Error Exit Sub1 and raise current Error in MainSub(?)
'Perform data checks
End Sub
Sub Sub2()
On Error Exit Sub2 and raise current Error in MainSub(?
'Modify data groups
End Sub
Sub Sub15()
On Error Exit Sub15 and raise current Error in MainSub(?
'Clean up work
End Sub
Is there anyway I can avoid having to do something like below for each of Sub1-Sub15?
Sub MainSub()
On Error GoTo NotifyandCorrect
Call Sub1
Call Sub2
...
Call Sub15
Exit Sub
NotifyandCorrect:
'Send copy of faulty file, the error code and Sub that caused it
'Then stop macro execution completely
End Sub
...
...
Sub Sub15()
On Error Goto HaltExecution
'Clean up work
Exit Sub
HaltExecution:
'Note Error message & type
'Note that Sub15 is where error occurred
End Sub
Closing Questions
Is this at all possible?
If this isn't possible, how should I handle this to do something like what I described? What would you suggest (please provide an example if you can)
You need to handle errors in your "child" methods, and have them "re-throw" the error (using Err.Raise in the error handler subroutine) so the caller gets to see it - when re-throwing, specify the method's name as the "source". The following code produces this output:
5 Invalid procedure call or argument DoSomething1
9 Subscript out of range DoSomething2
Public Sub MainSub()
On Error GoTo ErrHandler
DoSomething1
DoSomething2
Exit Sub
ErrHandler:
Debug.Print Err.Number, Err.Description, Err.Source
Resume Next
End Sub
Private Sub DoSomething1()
On Error GoTo ErrHandler
Err.Raise 5
Exit Sub
ErrHandler:
Err.Raise Err.Number, "DoSomething1", Err.Description
End Sub
Private Sub DoSomething2()
On Error GoTo ErrHandler
Err.Raise 9
Exit Sub
ErrHandler:
Err.Raise Err.Number, "DoSomething2", Err.Description
End Sub
Is there anyway I can avoid having to do something like below for each of Sub1-Sub15?
No. Each procedure must handle runtime errors, there's no way around it.
Specifying method names in hard-coded strings is annoying. By encapsulating each procedure into its own object (say, some ICommand implementation), you can achieve the same result by leveraging the TypeName function:
Module1
Option Explicit
Public Sub MainSub()
On Error GoTo ErrHandler
RunCommand New DoSomething1
RunCommand New DoSomething2
Exit Sub
ErrHandler:
Debug.Print Err.Number, Err.Description, Err.Source
Resume Next
End Sub
Private Sub RunCommand(ByVal command As ICommand)
command.Execute
End Sub
ICommand (class module)
Public Sub Execute()
End Sub
DoSomething1 (class module)
Option Explicit
Implements ICommand
Private Sub ICommand_Execute()
On Error GoTo ErrHandler
Err.Raise 5
ErrHandler:
Err.Raise Err.Number, TypeName(Me), Err.Description
End Sub
DoSomething2 (class module)
Option Explicit
Implements ICommand
Private Sub ICommand_Execute()
On Error GoTo ErrHandler
Err.Raise 9
ErrHandler:
Err.Raise Err.Number, TypeName(Me), Err.Description
End Sub
The ICommand interface isn't really needed, but formalizes the way each DoSomething command is to be called. The idea is to have an object to implement each procedure - that way you can have TypeName(Me) as your error source, and never need to hard-code a string. You'll have 15 methods in 15 dedicated class modules, instead of 15 procedures in a single standard module.
You can use Err.Number and Err.Description to get info about the error.
Next, I would suggest creating a temp string and updating it whenever a new sub is entered. such as:
Sub Sub1()
temp= "sub1"
...
End Sub
Sub Sub2()
temp= "sub2"
...
End Sub
So whenever an error is handled, the string temp holds the value of the sub it occurred in.
I have the following code in ThisOutlookSession. It runs when an item is added to my sent email folder. It fires like it's supposed to but it keeps telling me that 'the argument isn't optional'. I've looked at a lot of examples and used it to build this one. I can't figure out what I am doing wrong. Looks like I am doing everything by the book but still my item argument in myItems_ItemAdd still comes up empty.
Private WithEvents myItems As Outlook.Items
Private Sub Application_Startup()
Set myItems = Outlook.Session.GetDefaultFolder(olFolderSentMail).Items
End Sub
Private Sub myItems_ItemAdd(ByVal Item As Object)
Dim olNS As Outlook.NameSpace
On Error GoTo ErrorHandler
Dim Msg As Outlook.MailItem
If TypeName(Item) = "MailItem" Then
Set Msg = Item
If InStr(Msg.Body, "[W]") > 0 Then
FlagWaitingForAnswerAndMove (Msg)
End If
End If
ProgramExit:
Exit Sub
ErrorHandler:
MsgBox Err.Number & " - " & Err.Description
Resume ProgramExit
End Sub
Did you try to debug the code? Do you get any errors?
Most probably the following condition returns false and you consider it as empty:
If TypeName(Item) = "MailItem" Then
Is that the case?
Try to remove the mentioned condition. Does it help?
Why would I want to get out of an Error Handler (after handling) with an Exit Sub instead of just letting it go to the End Sub?
I'm sure it's simple. I just don't understand. Thanks for any help.
Example:
Public Sub SubA()
On Error Goto ProcError
''# other code
MsgBox FuncA()
ProcExit:
Exit Sub
ProcError:
MsgBox Err.Description
Resume ProcExit
End Sub
Your ProcExit label is your place where you release all the resources whether an error happened or not. For instance:
Public Sub SubA()
On Error Goto ProcError
Connection.Open
Open File for Writing
SomePreciousResource.GrabIt
ProcExit:
Connection.Close
Connection = Nothing
Close File
SomePreciousResource.Release
Exit Sub
ProcError:
MsgBox Err.Description
Resume ProcExit
End Sub
Typically if you have database connections or other objects declared that, whether used safely or created prior to your exception, will need to be cleaned up (disposed of), then returning your error handling code back to the ProcExit entry point will allow you to do your garbage collection in both cases.
If you drop out of your procedure by falling to Exit Sub, you may risk having a yucky build-up of instantiated objects that are just sitting around in your program's memory.