Yes, Access is used and it cannot be changed.
I have a form class object, let's say:
Option Compare Database
Private Sub cmdCalculate_Click()
Dim Employee As String
Employee = InputBox("Enter Name of Employee")
If InStr(Employee, "Eka") > 0 Then
Call Hello
Else
Call Hello2
End If
End Sub
And I have one module. As you can see, I call each procedure from my form.
Option Compare Database
Option Explicit
Public Sub Hello()
MsgBox "Hello Eka"
End Sub
Public Sub Hello2()
MsgBox "Hello stranger"
End Sub
The issue I have is with the error handling implementation as here we have the subsequent procedures which I call. I tried to add a simple On Error GoTo - see below to an individual sub-procedure to display a nice message and break the entire script execution but yes, the sub procedure will show a nice message, you click OK to close it and the main script just continues running. Can you, please, direct me to a source where I can read more on the potential solution or assist with it? I found something about global error handling, but not sure if it is relevant.
Private Sub cmdCalculate_Click()
On Error GoTo errormessage
#TO-DO. VBA Code
Exit Sub
errormessage:
MsgBox "An error has occured. Please check your work."
End Sub
Here is a small sample.
MainProcedure calls SubProcedure1 and this calls SubProcedure2.
In SubProcedure2 there will be a division by zero error.
SubProcedure2 handles this error and reraise it to the upper procedure in the call stack: SubProcedure1.
SubProcedure1 also handles it and also reraises it, now to MainProcedure.
MainProcedure now shows the error. It can stop execution now if you want that.
Remark1: VBA unfortunately has no call stack information you could read at runtime. So in my example I always add the current procedurename as a new line to the top of the source property of the error.
So finally you can see where the error happened and how the call stack was.
But that is just an example.
Remark2: If you, for example, wouldn't place an active error handler in SubProcedure1 the error would bubble up itself to MainProcedure, but then you couldn't add your call stack information.
Public Sub MainProcedure()
On Error GoTo Catch
SubProcedure1
Finally:
Exit Sub
Catch:
MsgBox Err.Number & " : " & Err.Description & vbNewLine & vbNewLine & "- MainProcedure" & vbNewLine & Err.Source, vbCritical
Resume Finally
End Sub
Public Sub SubProcedure1()
On Error GoTo Catch
SubProcedure2
Finally:
Exit Sub
Catch:
Err.Raise Err.Number, "- SubProcedure2" & vbNewLine & Err.Source, Err.Description
Resume Finally
End Sub
Public Sub SubProcedure2()
On Error GoTo Catch
Dim value As Long
value = 0
Dim value2 As Long
value2 = 1 / value
Finally:
Exit Sub
Catch:
Err.Raise Err.Number, "- SubProcedure2" & vbNewLine & Err.Source, Err.Description
Resume Finally
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 have a global template with a customized ribbon. When I try to open a document, I receive error 4248, which claims the command can't run because no document is open.
The code in the onLoad sub is checking to make sure that the active document isn't the template or Normal.dotm because I don't want document variables written to those files via the called procedures. This worked fine in Word 2013, but I'm noticing that in Word 2016, it seems the ribbon onLoad procedure is running before a document in Word is open.
I've pinpointed the issue to the ActiveDocument.Name line, where I can trap the error and resume next, but that doesn't help me run the additional code after the determination is made that this isn't the global template document or Normal.dotm.
Public myRibbonNewNormal As IRibbonUI
Public bVisible As Boolean
Dim bDocSaved As Boolean
Sub onLoad_newNormal(ribbon As IRibbonUI)
Set myRibbonNewNormal = ribbon
On Error GoTo onLoadError
If ActiveDocument.Name = "Styles.dotm" Or ActiveDocument.Name =
"newNormal.dotm" Then
Exit Sub
ElseIf ActiveDocument.ReadOnly Then
Exit Sub
Else
'Call checkDocType
Call uncheckUpdateStyles
Call removeClientFooter
Call checkTemplate
If bDocSaved <> True Then
Call preventSave
Else
'do nothing
End If
End If
Exit Sub
onLoadError:
If Err.Number = 0 Then
Resume Next
ElseIf Err.Number = 5 Then
Resume Next
ElseIf Err.Number = 5825 Then
Resume Next
ElseIf Err.Number = 5903 Then
Resume Next
'ElseIf Err.Number = 5155 Then
' Resume Next
ElseIf Err.Number = 4248 Then
Resume Next
Else
MsgBox "This error is in the onLoad sub in the newNormal RibbonControl" _
& vbCrLf & vbCfLf & "Error: " & Err.Number & vbCrLf & Err.Description, ,
"Error"
End If
End Sub
(Please excuse my error handling. I was in a hurry when I wrote this code and just haven't gotten back around to doing it properly.)
Is there a way for me to wait to execute the onLoad sub until Word has opened the document (by simple double-click)? I'm sure I'm missing something here, but I'm struggling to find any information about this online.
Move the code to an application level event handler. The code will run each time a new document is opened, but that can be remedied if needed (see code comments).
Create a new class module and name the class module EventMngr.
Place the following code in the class module:
Public WithEvents appevent As Application
Private Sub appevent_DocumentOpen(ByVal Doc As Document)
'This code will only allow the event to fire once
Static hasOpened As Boolean
If hasOpened Then Exit Sub Else hasOpened = True
'Place Code Here...
End Sub
Then you can place this line in a normal module before any other subs/functions
'application level event handler
Public myEventHandler As New EventMngr
And last, place this in your Ribbon OnLoad sub:
'application event handler
Set myEventHandler.appevent = Application
Similar to the previous answer but possibly simpler is to add autoopen and autonew procedures to your code and capture the activedocument in those procedures rather than the onload procedure.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 5 years ago.
Improve this question
I have a button that runs several subs one after another:
Private Sub CommandButton2_Click()
Application.Run ("sub1")
Application.Run ("sub2")
Application.Run ("sub3")
Application.Run ("sub4")
End Sub
I have checks in each subs that check if a file that is to be opened exists, if yes, it goes on, if no, it Exit Subs. But the problem is, it exits only the current sub, but all the next are being executed which might result in a mess.
Is there a way to exit all the subsequent subs?
An example
if sub1 then sub2
using a function like so, the last one can remain as a sub
public function sub1() as Boolean
dim blnFileExists as Boolean
....file checking
sub1=blnFileExists
if blnFileExists then exit function
....go on
end function
if yes, it goes on, if no, it Exit Subs.
In all cases, your procedures completed successfully, as far as the caller is concerned.
Seems like a case of "let it bubble up" to me. I presume you procedures look something like this:
Private Sub DoSomething()
On Error GoTo CleanFail
'do stuff
CleanExit:
'clean up resources
Exit Sub
CleanFail:
Debug.Print Err.Number, Err.Description
'handle error (?)
Resume CleanExit
End Sub
So the caller has no way of knowing whether the operation succeeded or not.
If it doesn't make much sense to make them return a Boolean indicating success (i.e. turning them into Function procedures and returning False on error - which is NOT always a good solution, especially if the error path is truly exceptional), then the other solution is to not let the "happy path" carry on:
Private Sub DoSomething()
On Error GoTo CleanExit
'do stuff
CleanExit:
'clean up resources
With Err
If .Number <> 0 Then .Raise .Number, "DoSomething", .Description
End With
End Sub
Now your calling code gets to know what happened (you could raise custom errors, include the procedure as the Source, etc.), and can act accordingly:
Private Sub CommandButton2_Click()
On Error GoTo CleanFail
DoSomething
DoSomethingElse
DoAnotherThing
Exit Sub
CleanFail:
With Err
MsgBox "Error " & & .Number & " in " & .Source & ": " & .Description, vbExclamation
End With
End Sub
Here's what worked for me:
Function sub1()
...
Dim result As Boolean
result=True
If (fileThereIs = False) Then
result=False
End If
...
sub1=result
End Function
Private Sub CommandButton2_Click()
Dim fileCheck As Boolean
fileCheck = Application.Run("sub1")
If (fileCheck = True) Then
Application.Run ("sub2")
Application.Run ("sub3")
Application.Run ("sub4")
End If
End Sub
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.
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.