Is there some VBA best practice to use something like 'before end' subroutines?
I am changing Excel's default configs when my macro starts, and before my macro reaches its 'end sub' line I am resetting the configs to its standards.
But what if some error occurs? Am I supposed to define 'On Error' treatment inside all subs to reset the configs to the standard properties?
Just for example, I am changing configs such as:
ScreenUpdating
DisplayStatusBar
Calculation
DisplayAlerts
I'm pretty sure there is no such mechanism that is called unconditionally before exiting a function or a subroutine. You may have though error handlers (but these are executed conditionally; see the comment of ckuhn203 for an example).
However, there is such a mechanism for instances of Class Modules (i.e. for objects). When an object is destroyed (this happens when is not referenced anymore by any variable/storage), its Class_Terminate subroutine is called no-matter-what. If you can wrap your task in such an object that you discard immediately after you create it, you could override overwrite this subroutine to do the cleanup.
If I understand your question correctly, yes, the best way is to define an On Error Goto line, in each method where it's needed, like this:
Public Sub DoSomething()
On Error GoTo Finally ' Try
Application.ScreenUpdating = False
' Do your stuff here
Finally:
Application.ScreenUpdating = True
End Sub
This will ensure the things like ScreenUpdating get done even if there is an error. You can also add a catch block, like this:
Public Sub DoSomething()
On Error GoTo Catch ' Try
Application.ScreenUpdating = False
' Do normal stuff here
GoTo Finally
Catch:
' Do only error stuff here
Finally:
Application.ScreenUpdating = True
End Sub
Generally speaking, GoTo is a hated practice, but for error catching, VBA kind of forces your hand.
Related
I have a bit of an oddball problem. I use a routine to check focus; and ... it stopped working. I have no idea how or why.
The routine basically checks to see if the active control is the one you're checking, and if so, returns true (so we can handle the cases where it's not).
However...it recently started returning false all the time (we didn't change anything, we only noticed when some field auditing started returning weird values). Even when the control is focused, and if there's no other controls on the form, or only one form open, and the form clearly has focus.
Does anyone have any ideas how or why this might be? It's confounding me. As you can see, I've got a test field, where we're running an init in it...and the values clearly match, name, values, every field compared, and it still doesn't return true.
What am I doing wrong?
Edit: forgot to add the code.
The whole thing as-is:
' I call it from here:
' Inside form, any control, say `PurchaseCostBox`
Private Sub PurchaseCostBox_AfterUpdate()
' Check if the focus is had
If VerifyFocus(Me.PurchaseCostBox) Then
' Save more field info.
Debug.Print Me.PurchaseCostBox.SelStart
Debug.Print Me.PurchaseCostBox.SelLen
Debug.Print Me.PurchaseCostBox.Value
Else
' Do limited stuff
Debug.Print Me.PurchaseCostBox.Value
End if
End Sub
Public Function VerifyFocus(ByRef ctlWithFocus As Control) As Boolean
Dim FrmParent As Form
Dim ctlCurrentFocus As Control
On Error Resume Next
' Determine parent form for control
' Verify focus of parent form
Set FrmParent = Screen.ActiveForm
' Verify focus of control on form
Set ctlCurrentFocus = FrmParent.ActiveControl
If Not ctlCurrentFocus Is ctlWithFocus Then
ctlWithFocus.SetFocus
DoEvents
End If
' Even adding the below line does not return true:
ctlWithFocus.SetFocus
' Return true if the control currently has the focus
VerifyFocus = FrmParent.ActiveControl Is ctlWithFocus
' Discard any errors
Err.Clear
End Function
I've also had it try this:
Public Function VerifyFocus(ByRef ctlWithFocus As Control) As Boolean
On Error Resume Next
' Return true if the control currently has the focus
VerifyFocus = Screen.ActiveControl Is ctlWithFocus
' Discard any errors
Err.Clear
End Function
Neither work any more...and I'm floundering.
Well, this turned out to be something utterly unexpected, and totally unrelated to the focus.
Turns out, one of the ways I call this is by getting a control's parent, by using Control.Properties.Parent.Form. While this DOES return the correct form, it also makes the above VerifyFocus routine never return true, ever (even when it's not being used). I don't know why. I really, at this point, don't care. But I'm going to leave it here for others to find.
Refactoring my GetTopForm routines allowed me to get the focus.
Anyone else finding that their Terminate() method in Access isn't being called?
Here's my code for my cBusyPointer class with the comments removed for brevity:
Option Compare Database ' Use database order for string comparisons
Option Explicit ' Declare EVERYTHING!
Option Base 1 ' Arrays start at 1
Public Sub show()
DoCmd.hourGlass True
End Sub
Private Sub Class_Terminate()
DoCmd.hourGlass False
End Sub
Typical usage is:
Sub doTehThings()
Dim hourGlass As New cBusyPointer
hourGlass.show()
' Do all teh things
End Sub
In previous versions, this class would restore the hourglass whenever the object went out of scope and was destroyed.
I even tried to manually destroy my hourglass object:
Sub doTehThings()
Dim hourGlass As cBusyPointer
Set hourGlass = New cBusyPointer
hourGlass.show()
' Do all teh things
Set hourGlass = Nothing
End Sub
The only way to fix this is to add a hide() method and call that.
Has anyone else encountered this problem?
I cannot replicate the issue. The Terminate() method is called upon reaching the Set hourGlass = Nothing.
A couple of points:
Dim hourGlass As New cBusyPointer
This will create a new instance every time you call the hourGlass variable even to set it to Nothing. See the answer in the link below for additional info:
What's the difference between Dim As New vs Dim / Set
You should always use Dim/Set when working with objects.
hourGlass.show()
This does not even compile in VBA. Subs do not accept parentheses even when arguments are being expected, unless they are preceded with the Call keyword.
Lastly, the cleanest way to reference an object is to access it using the With statement which ensures the object is terminated when the End With statement is reached.
With New cBusyPointer
.show
End With
I have found similar questions, but for other issues, seems that Excel is called from another application and process is left after Excel application closes.
In my case, I have a macro in my Excel file, and I try to close the application when an error occurs.
I have my error handling set this way:
'Code code code
CleanExit:
Logger.LogData LOG_DEBUG, MODULE_NAME, "Initialize", "Some module initialized!"
Exit Function
ErrorExit:
Logger.LogData LOG_ERROR, MODULE_NAME, "Initialize", "Error found! Description: " & err.description
Main.HandleError err, MODULE_NAME, "Initialize"
GoTo CleanExit
End function
I want my macro to stop running when error occurs in some module and not to stop if it's in another module (hence the GoTo CleanExit).
Error handler is set-up in this way:
Public Function HandleError(ByRef err As ErrObject, ByVal moduleOrgin As String, ByVal methodOrgin As String)
Dim wbk As Workbook
'Do something if module origin meets my parameters and exit function right here if my conditions are met
MsgBox "Some message to the user about the problem"
If GetSetting(SETTING_HIDE_APPLICATION, False) = True Then
For Each wbk In addinWorkbook.Application.Workbooks
wbk.Saved = True
Next wbk
addinWorkbook.Application.Quit
End If
End Function
After this code runs I assume that all further code running stops, as my Excel workbook, which hosts my macro code is closed with the application.
In reality I get a cascade of errors, where the error message is shown 3 times until it closes for good. How can I avoid code any code running after Application.Quit method?
Code workflow when error occurs and what runs after Application.Quit:
Main method to initialize my form
Call to loader method which throws error (Application should quit here)
Main method continues after loader method is finished
Subsequent method is called from main method which also throws an error (because first loader failed)
Lastly my main method throws an error
In total I receive 3 msgboxes with error descriptions.
I must note, that I use the same error handling procedure in all methods, but I would like the code to stop executing, so further code does not trigger any errors.
How can I avoid code any code running after Application.Quit method?
If you want to stop everything, write End after Application.Quit. It stops every piece of VBA and kills all variables you have assigned. This is not considered a good practice (At all!), but it will work exactly as you want.
I have this module here that has is Workbook subroutine. I can't for the life of me understand how the GenerateLimitSummary is ever able to run? Can someone please articulate the process flow here?
Private LimitBool As Boolean
Private Sub Workbook_SheetCalculate(ByVal Sh As Object)
If LimitBool Then Exit Sub
' use conditional formatting to highlight limit breaches
ApplyConditionalFormatting
' regenerate the summary limits sheet
LimitBool = True
GenerateLimitSummary
LimitBool = False
End Sub
The author uses LimitBool to prevent a infinite loop/a stack overflow:
Initially, LimitBool is False, therefore the remainder of Workbook_SheetCalculateis executed
Now, LimitBool is set to True (after it was confirmed it's not True)
GenerateLimitSummary is executed. If this routine now for some reasons forces the workbook to recalculate, Workbook_SheetCalculate will be triggered again. However, as LimitBool is now True*, the second call to this procedure is now Exited after the first check. If it would not have this check, it would again call GenerateLimitSummary, which would then trigger the recalc, etc...
After the GenerateLimitSummaryran, LimitBool is set back to False, therefore, it can ran again
(*) - it has a Module-wide scope, i.e. it keeps it value across the different calls, while a procedure-wide scope (=Dimmed in the sub) would create a new variable for each call
I have a crash file where I can see that one of my own VB6 user controls is responsible for the crash; i.e. one of its methods is part of the stack trace and I can see the line responsible.
From here, I'd like to inspect the state of its member variables. How do I do this?
Note: I also have the private symbols for my controls. The problem is being able to inspect "Me". The command !object address_of_Me doesn't seem to do the trick and so I'm at a loss.
Thank you.
It's been 10 years since I had to do this in VB6, but I remember a lot of Printer.Print statements in my past life :)
I used to do things like this for debugging (but not for release code)
Sub MySub
On Error Goto ErrorTrap
Dim intX as integer
Dim intY as integer
' do some horrible error here
Exit Sub
ErrorTrap:
Printer.Print "Error"
Printer.Print intX
Printer.Print intY
Printer.Print ...
End Sub
well, codeSMART have one option install global handle on your application first call to SetUnhandledExceptionFilter (win api) is should be installed when load your module main or master form when is closing the program so call to SetUnhandledExceptionFilter.
The code is little long so copy methods names y api calls
Public Sub InstallGlobalHandler()
On Error Resume Next
If Not lnFilterInstalled Then
Call SetUnhandledExceptionFilter(AddressOf GlobalExceptionHandler)
lnFilterInstalled = True
End If
End Sub
Public Sub UninstallGlobalExceptionHandler()
On Error Resume Next
If lnFilterInstalled Then
Call SetUnhandledExceptionFilter(0&)
lnFilterInstalled = False
End If
End Sub
Also here is Record Structure y apis declarations for the module
- CopyMemory
- SetUnhandledExceptionFilter
- RaiseException
' Public enums
-EExceptionType
-EExceptionHandlerReturn
-Private Const EXCEPTION_MAXIMUM_PARAMETERS = 15
' Private record structure
-Private Type CONTEXT
'Structure that describes an exception.
-Private Type EXCEPTION_RECORD
'Structure that contains exception information that can be used by a debugger.
-Private Type EXCEPTION_DEBUG_INFO
-Private Type EXCEPTION_POINTERS
Take a revised that How to route the exe exception back to VB6 app?