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.
Related
Is there a way that vba can determine if a method is present/available?I have two different methods that will essentially be doing the same thing, but in different ways: one being an SQL server and the other being an excel file. I want to be able to first call the SQL method, and if whatever reason that SQL method is unavailable, it will then call the excel method. But what would be the line of code in the if statement?
I know that you "Call" to methods, but what essentially is the line of code that would be:
If SQLMethod() ... Then
Call SQLMethod()
Else
Call ExcelMethod()
End If
What would the code be at "..." to check this?
Any help would be appreciated!
You cannot have a reference to a routine that doesn't exist; the code won't compile, and you'll receive a "Complie error: Sub or Function not defined".
But, you can do this:
Sub Main()
On Error Resume Next
Application.Run "Test1"
If Err <> 0 Then Application.Run "Test2"
End Sub
Sub Test1()
Debug.Print "Test1()"
End Sub
Sub Test2()
Debug.Print "Test2()"
End Sub
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...
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
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
I'm looking for a bit of advice here.
I have a spreadsheet with various modules and procedures that can be called from a Worksheet_Change event. This causes problems when I need to issue a sheet from the workbook for other users to complete.
Whenever the user tries to update the sheet, the on change event gets triggered, causing a compile error as the procedure being called does not exist, and this cannot be trapped (as far as I'm aware). I've tried using Application.EnableEvents = False, but this is in the worksheet event and the code breaks as soon as the event is triggered.
Is there anyway to call a procedure through late binding where I can trap the error?
I'm trying something like this at the moment.
Dim mdl as object
' Test for module in workbook, if error, then exit routine
Set mdl = Application.ActiveWorkbook.VBProject.VBComponents("mdlSharedFunctions")
'If no error, then call procedure here
call mdl.UpdateData(Target)
'Or
Application.Run mdl.UpdateData(Target)
Neither of these call methods will work and I'm hoping someone out there will be able to point me in the right direction.
Cheers
Pete
You can use a global variable as a flag - bit dirty but it works fine. Then add an If flag = true then statement to the change event sub.
Public globalflag as Boolean
Sub test1()
If globalflag = True Then
BrokenSub 'This sub has an invalid sub/function referenced, but will be ignored if the flag is set to false
Else
'Don't run the code
Exit Sub
End If
End Sub
Sub BrokenSub()
invalidfunction ("asb")
End Sub
EDIT
To put it in a worksheet, just see if the variable exists:
Declare this in a module in your master spreadsheet:
Public globalflag as Boolean
Then in your worksheet code
If not IsEmpty(globalflag) Then
BrokenSub 'Put your master spreadsheet code here - it'll run if globalflag exists and be ignored if it doesn't
End If
Sub BrokenSub()
invalidfunction ("asb")
End Sub