Cancelling a child subroutine via errorhandling from a parent subroutine - excel

Background:
I'm attempting to plan out how to automate a task and am anticipating using multiple private subroutines, or functions, from another subroutine. I'll use parent-child language to try and keep things straight.
The parent subroutine is the one calling each of the children.
The child subroutines (children) will be called in order to perform their code.
I am trying to determine if I can have error handling in the parent to exit sub for a child and move to the next child.
Albeit the example code I will use is a very simple example of just adding 1 to i, the planned code would be more involved where resume next wouldn't be sufficient (not that ignoring an error is "good" practice).
Issue:
I am not having luck in searching if there is an existing on error to exit the current child and move to the next, where I have my on error line only in the parent.
Question:
Is there a way to exit sub the current child based on an error handling statement in the parent subroutine? Or, should I just stick to error handling in each child?
Code in Question:
Giving an gross example of what I am thinking; this is not working code.
Option Explicit
Public i As Long
Sub fdsa()
on error ' exit called subroutine
a
s
d
f
MsgBox i
End Sub
Private Sub a()
i = i + 1
End Sub
Private Sub s()
i = i + 1
End Sub
Private Sub d()
i = i + 1 / 0
End Sub
Private Sub f()
i = i + 1
End Sub
The messagebox would output "3" after the parent finishes.

If the child routines have no error handler, then any errors will be passed back to the parent, at which point you can resume with the next one. All you need is on error resume next here:
Sub fdsa()
on error resume next
a
s
d
f
MsgBox i
End Sub

Related

Is there a way to tell if the Sub currently running is the parent or a called child

I have routines in VBA that can run standalone or can be called by another routine. When called as a subroutine (child) or as a primary routine (parent) there are operations that I may or may not want to execute. Is there a built-in function in VBA that I can call that will tell me if my executing code is a parent or child?
I have created global variables to test for parent/child but I would like to have a more elegant solution.
I think a good way would be to have a procedure for the action itself that has a switch (parameter) and a procedure that calls it.
Private Sub MyProcedure(Optional ByVal IsChild As Boolean = True) 'set default here
If IsChild Then
'child
Else
'parent
End If
End Sub
Now you can have a procedure to call it
Public Sub ParentCallMyProcedure()
MyProcedure IsChild:=False
End Sub
Public Sub ChildCallMyProcedure()
MyProcedure IsChild:=True
'which would be the same as
MyProcedure
End Sub
Eg if you want to call MyProcedure from a button then use
Public Sub Button1_Click()
MyProcedure IsChild:=False
End Sub
In all other procedures just use MyProcedure and IsChild is default True.
At least this is more elegant than a public/global variable.
In .Net getting info for a method, which called a method is called Reflection. It is quite straight-forward in C# - How can I find the method that called the current method?. VBA does not support it, but you could run around it, and log somewhere data about it - through a variable or logging to a worksheet or database.
There is a way to do it, if you stop the code in the middle of the called sub/function and call the Call Stack diaglog box. E.g., imagine the following seqeuence:
Sub TestMe()
Testme2
End Sub
Sub Testme2()
Stop
End Sub
If you run the code and press Ctrl+L once you are on the Stop you would get this:
If you only run TestMe2 and press Ctrl+L, you would get it correspondingly:
While the following is not really a solution it may work depending on your setup:
This is my Occam's Razor solution that I have used in the past.
Public ChildCount as Integer
Sub EveryProc()
ChildCount = ChildCount + 1
... rest of code...
ChildCount = ChildCount - 1
End Sub
This allows me to test how far I am into subroutines as ChildCount will be 1 for the parent and >1 for the children. I think the first time into the VBA, ChildCount will be zero so you need to increment and decrement the variable at the beginning and end of every sub.
I am spoiled in SAP...

Late binding Microsoft Windows Common Control (TreeView) object

I have implemented a DragDrop functionality to my Excel database using TreeView control, using this code:
Private Sub TreeView1_OLEDragDrop(Data As MSComctlLib.DataObject, Effect As Long, Button As Integer, Shift As Integer, X As Single, Y As Single)
Dim StrPath As String
StrPath = Data.Files(1)
'path saved in UserForm label named "FilePathLB"
FilePathLB = StrPath
End Sub
It works perfectly fine on most of the machines I distributed the file to, however some machines with older versions of MS Office fire an error on the very first line (Private Sub ...) due to not being able to find Microsoft Windows Common Control library.
My question: is it possible to late bind this library and thereby preventing the error from happening?
Or at least, is it possible to add a debugger to prevent the error from showing, something like On Error Resume Next for the whole Sub? I understand that in this case the DragDrop function would not work, but it is better than an error.
For your last question:
Sub ()...
On Error GoTo ErrorHandler
'Your code
Exit Sub
ErrorHandler:
Msgbox "Could not load DragDrop function. Program execution has been terminated.", vbExclamation, "Error"
End Sub
If you want to, you could also just drop the MsgBox.
EDIT:
will not work as the code breaks on the first line.
Code below to support my comment. If an error occurs in the sub-macro, then the PassedSub variable won't be set to True, thus indicating an error.
Public PassedSub As Boolean
Sub test1()
On Error Resume Next
Call test2
If PassedSub = False Then GoTo ErrorHandler
On Error GoTo 0
Exit Sub
ErrorHandler:
MsgBox "Could not load DragDrop function. Program execution has been terminated.", vbExclamation, "Error"
End Sub
Sub test2()
Debug.Print 2 / 0
PassedSub = True
End Sub

How do I break on errors?

I have a Function that has some bug in it somewhere causing it to return #VALUE when I try to execute it in excel.
I have no idea where the error is, and stepping through the code is just tedious. So I'd like the debugger to break as soon as an error occurs.
I tried going to Tools->options->General->"Break on All Errors" but noticed no change.
How do I get the VBA IDE to break on an error?
Just add an error handler in your function like the one below.
If an error occurs, the IDE will print the error description in the immediate window and stop on Debug.Assert 0.
Then press F8 two times to go to the line where the error occured.
Function Test() As Variant
On Error GoTo ErrHandler
Dim v(), n&, r&, c&
For r = 1 To 3
For c = 1 To 4
n = n + 1
ReDim Preserve v(1 To r, 1 To c)
v(r, c) = n
Next c
Next r
Test = v
Exit Function
ErrHandler:
Debug.Print Err.Number & ": " & Err.Description
Debug.Assert 0
Resume
End Function
Something like:
Public Function dividddeee(a As Variant, b As Variant) As Double
On Error GoTo wtf
dividddeee = a / b
Exit Function
wtf:
On Error GoTo 0
MsgBox "Houston, we've had a problem here"
MsgBox a & vbCrLf & b
End Function
If you add error handlers, you can take advantage of Debug.Assert to force a break if you don't want the standard handler to execute. You can define a compiler constant to just let your error handlers deal with it when you release it to the wild. If you want to see what specific line caused the error, you can put Resume Next after the Debug.Assert. When you step through it, it will take you to the line immediately after the one that caused the error.
Drop this small demo into Module and run the Sub with both Release = True and Release = False for an example:
Option Explicit
#Const Release = False
Private Sub Demo()
Debug.Print DivByZero(5)
End Sub
Public Function DivByZero(inValue As Integer) As Double
On Error GoTo Handler
DivByZero = inValue / 0
Exit Function
Handler:
#If Release Then
MsgBox Err.Description & " in DivByZero"
#Else
Debug.Assert False
Resume Next
#End If
End Function
If you call a VBA function as UDF the settings of the VBA IDE are not involved. So no chance for error debugging this way.
Try calling the function from a test Sub. Then the error debugging will work.
But there are some things a function cannot do as a UDF but can do called from a Sub. If one of those things is the reason for the #VALUE error, then no way around setting a breakpoint in the function and stepping forward until the next step is not possible. The last line in stepping is the error line.
You should really mention if the function is
Called from an Excel cell.
Has an event handler.
Show how your variables are declared.
If called from a cell, the inputs to the function can cause you problems with different inputs. Sometimes preventing the call of the function if the types significantly change to something unexpected. For example a parameter declared as variant in its signature will pass in an error but fail in the function. You may be trapping this error and returning #VALUE in the code. No way for us to know that.
If you have an event handler, for a quick test, you could put a 'Stop' in the event handler to stop like you are asking. If not you can put one in as already stated. Assertions are nice, I like them and use a lot of them - but here since you know the function and are working on this explicit problem a Stop should be good enough for your testing. Just don't save it for production code. Make a Test copy of the book.

How to stop a VBA macro without destroying objects

I have written a VBA program which creates, on Workbook_Open, a file stream that writes errors to an error log. If I run into a fatal error (and therefore need to halt execution of the macro), the program executes an End statement, abruptly halting the macro. I know that this solution is not an ideal one, but I can't see a better way to end things quickly if I'm several functions deep (i.e., one function has called another which has called yet another, and the third function produces the error). Exiting the function in which the error occurred will only affect that particular function, leading to the possibility of invalid data, unexpected cell values, etc.
But this approach leads me to another problem -- when End executes it destroys all of my objects, including the error stream. So when the user does something new and runs across a fatal error, they get a VBA runtime error (91: Object ... not set) because the code writes to the file stream that's now set to Nothing.
Is there a better way to end the macro (and thus avoid unexpected behaviour after an error) without losing all of my objects? The official VBA documentation is of no help. Thanks in advance.
End:
Terminates execution immediately. Never required by itself but may be
placed anywhere in a procedure to end code execution, close files
opened with the Open statement and to clear variables. When executed,
the End statement resets all module-level variables and all static
local variables in all modules.
ThisWorkbook module:
Public fileSystem As FileSystemObject
Public errorStream As TextStream
Private Sub Workbook_Open()
Set fileSystem = New FileSystemObject
Set errorStream = fileSystem.CreateTextFile("c:\temp\error.log", True)
End Sub
Standard module:
Public Sub First()
If (Not ThisWorkbook.errorStream Is Nothing) Then
Debug.Print VBA.TypeName(ThisWorkbook.errorStream)
End If
End
' Exit Sub
End Sub
Public Sub Second()
If (Not ThisWorkbook.errorStream Is Nothing) Then
Debug.Print VBA.TypeName(ThisWorkbook.errorStream)
End If
End Sub
When 'First' method executes first with 'End' in it and then 'Second' method, then errorStream will be Nothing. Instaead of 'End' use 'ExitSub', then the variable will not be reset.
Or you could make error-stream variable private in Thisworkbook class module and add property, which will create the stream if the variable is Nothing. HTH
ThisWorkbook module:
Private m_errorStream As TextStream
Private Const FILE_PATH_NAME As String = "c:\temp\error.log"
Public Property Get ErrorStream() As TextStream
If (m_errorStream Is Nothing) Then
Dim fileSystem As FileSystemObject
Set fileSystem = New FileSystemObject
If (fileSystem.FileExists(FILE_PATH_NAME)) Then
Set m_errorStream = fileSystem.GetFile(FILE_PATH_NAME).OpenAsTextStream
Else
Set m_errorStream = fileSystem.CreateTextFile(FILE_PATH_NAME, False)
End If
End If
Set ErrorStream = m_errorStream
End Property
Standard module:
Public Sub First()
If (Not ThisWorkbook.ErrorStream Is Nothing) Then
Debug.Print VBA.TypeName(ThisWorkbook.ErrorStream)
End If
End
End Sub
Public Sub Second()
If (Not ThisWorkbook.ErrorStream Is Nothing) Then
Debug.Print VBA.TypeName(ThisWorkbook.ErrorStream)
End If
End Sub
Declare your variables on the module-level, not in the function. In VBA, you see the various sheets on the Project navigation on the left by default. Below the sheets is a folder called "Modules": if you don't see a "Module1" or variant as a child of this folder, right-click the folder and select "Insert\Module."
These should be persistent for you.
Maybe try Exit instead of End?
If you are trying to exit from a function
Function a()
If blahblah.. Then
Exit Function
End If
End Function
I am unsure of how your objects are declared and handled on what modules so... if you can post the code, it may help greatly.
use a top level error handler, and only trap errors in routines you want to handle without aborting completely
Demo:
Option Explicit
Sub test()
On Error GoTo Top_Error_Handler
Debug.Print "Error handled in sub routine: test1"
test1
Debug.Print "Error NOT handled in sub routine: test2"
test2
Exit Sub
Top_Error_Handler:
MsgBox "Top Level Error Handler: Error Number:" & Err.Number _
& ":" & Err.Description
End Sub
Sub test1()
On Error Resume Next
Debug.Print 1 / 0
End Sub
Sub test2()
Debug.Print 1 / 0
End Sub
As you can see, the error handling in test1 overrides the handling in the main program, so no error is raised. In the second Sub, test2, there is no error handling, so the information is passed up to the previous program to handle (and it will pass it up the chain, if that program was called by something else), and the error can be cleanly handled by your main routine to close everything tidily.

Single callback macro for multiple form controls (ListBoxes to be specific)

I'm trying to store selected values from a listbox (Excel 2010) with multi selection enabled. This is easily done by iterating through the items in the list to see if they are selected. However, upon adding a number of listboxes, i have to create a callback for each:
Sub ListBox1_Changed()
Call DoStuff(Worksheets("Sheet1").ListBoxes(1))
End Sub
Sub ListBox2_Changed()
Call DoStuff(Worksheets("Sheet1").ListBoxes(2))
End Sub
Sub DoStuff(L as ListBox)
'Do stuff here
Sub
Eventually I will end up with a large number of these ListBoxes across multiple worksheets.
Now my question is: Is it possible to reference the specific box that called the macro and assigning this single function for all my listboxes? I'm guessing something like:
Sub ListBox_Changed(ByVal L as Object)
' This will not work btw ^^^^^^^^
' Magic code goes here.
Call DoStuff(L_converted_to_ListBox_Format)
End Sub
Please note, that I'm not using userforms but have just put the listbox directly in the worksheet.
Thanks!
You can use the Application.Caller to determine which ListBox called the Sub, like this
Sub ListBox_Changed()
Dim v As Variant
Dim lb As ListBox
v = Application.Caller
On Error Resume Next
Set lb = Me.ListBoxes(v)
If Err.Number <> 0 Then Exit Sub
On Error GoTo 0
DoStuff lb
End Sub
Sub DoStuff(lb As ListBox)
Debug.Print lb.List(lb.Value)
End Sub

Resources