Conflicts arise in Excel VBA while running multiple Subroutines that modify the Application State. What happens is that the first Subroutine will turn off setting to speed up the code and then call a second Subroutine that does the same. The second Subroutine will then switch the setting back on before exiting. The original Subroutine then resumes in an incorrect Application State.
My solution uses a Static Scripting Dictionary to save and later restore the Application setting. The first call stores an array of the old setting in the Dictionary by a Key and applies the new settings. The second call uses the Key to look up the old settings and restore the Application State to its original setting before first call.
As a bonus it also print the execution time of the subroutines to the Immediate Window.
Public Sub AppState(Key As String, Optional CalcMode As XlCalculation, Optional ScreenUpdating As Boolean, Optional EnableEvents As Boolean)
Const TIME = 0, CALC = 1, UPDATING = 2, EVENTS = 3
Static settings As Object
With Application
If settings Is Nothing Then Set settings = CreateObject("Scripting.Dictionary")
If settings.Exists(Key) Then
Debug.Print Key; " Execution Time: "; Format((Timer - settings(Key)(TIME)) / 86400, "hh:mm:ss")
Debug.Print String(20, "-")
.Calculation = settings(Key)(CALC)
.ScreenUpdating = settings(Key)(UPDATING)
.EnableEvents = settings(Key)(EVENTS)
settings.Remove Key
Else
settings.Add Key, Array(CDbl(Timer), .Calculation, .ScreenUpdating, .EnableEvents)
.Calculation = CalcMode
.ScreenUpdating = ScreenUpdating
.EnableEvents = EnableEvents
End If
End With
End Sub
Demonstration Code
This crude example prints the Application Sate to the Immediate Window as it is modified by multiple subroutines.
Sub Main()
ResetEvents
PrintAppState "Original Settings: "
AppState "Main1", xlCalculationManual, False, False
PrintAppState "Main1 Settings"
Test1
Test2
AppState "Main1"
PrintAppState "After Main Settings Removed: "
End Sub
Sub Test1()
Application.Wait (Now + TimeValue("0:00:02"))
End Sub
Sub Test2()
PrintAppState "Test2 Start: "
AppState "Test2", xlCalculationAutomatic, True, False
PrintAppState "Test2 Settings: "
Application.Wait (Now + TimeValue("0:00:02"))
AppState "Test2"
PrintAppState "After Test2 Settings Removed: "
End Sub
Sub PrintAppState(Msg As String)
With Application
Debug.Print Msg
Debug.Print "ScreenUpdating: "; .ScreenUpdating
Debug.Print "Calculation: "; .Calculation
Debug.Print "EnableEvents: "; .EnableEvents
Debug.Print String(20, "-")
End With
End Sub
Immediate Window Output
Notice that the Application State is restored to it's previous setting after the same Key is used for a second time.
Original Settings: ScreenUpdating: True Calculation: -4105 EnableEvents: True -------------------- Main1 Settings ScreenUpdating: False Calculation: -4135 EnableEvents: False -------------------- Test2 Start: ScreenUpdating: False Calculation: -4135 EnableEvents: False -------------------- Test2 Settings: ScreenUpdating: True Calculation: -4105 EnableEvents: False -------------------- Test2 Execution Time: 00:00:02 -------------------- After Test2 Settings Removed: ScreenUpdating: False Calculation: -4135 EnableEvents: False -------------------- Main1 Execution Time: 00:00:04 -------------------- After Main Settings Removed: ScreenUpdating: True Calculation: -4105 EnableEvents: True --------------------
The timer was based off off: VBA Code To Calculate How Long Your Macro Takes To Run
As always, I welcome any suggestions and/or feedback.
Addendum
I refactored my code reading @this post and @jasoninvegas comments.
'---------------------------------------------------------------------------------------
' Procedure : SaveAppState
' Author : Thomas Inzina
' Date : 12/29/2017
' Purpose1 : To Save and Restore Excel's Application State one or more times
' Purpose2 : To Log State changes and Execution Time between calls
'-----------------------------------------Parameters------------------------------------
' Restore : Value->False or Missing: Saves the State Value-> True Restores the State
' Label : Adding a Label with Restore->False logs start time and Current State to be
' ' Saved. With Restore->True logs Execution Time, Current State and Restored
' ' State values
'------------------------------------------Example--------------------------------------
' SaveAppState False, "Start Test1" ' Saves the Application State
' Application.ScreenUpdating = False ' Change the state
' 'Code Here ' Do Work
' SaveAppState True, "End Test1" ' Restores then Application State
'---------------------------------------------------------------------------------------
Public Sub SaveAppState(Optional Restore As Boolean, Optional Label As String)
Const TIME = 0, CALC = 1, UPDATING = 2, EVENTS = 3, LINETEMPLATE = "| | | |"
Static settings As Object
Dim values() As Variant
Dim line As String
If settings Is Nothing Then Set settings = CreateObject("System.Collections.Stack")
With Application
If Restore Then
If settings.Count = 0 Then
Err.Raise 5, "Test", "The Stack is Empty " & vbCrLf & "An Application State must be Saved first."
Exit Sub
End If
values = settings.Pop
If Len(Label) > 0 Then
Debug.Print Label; "-> Execution Time: "; Format((Timer - values(TIME)) / 86400, "hh:mm:ss")
Debug.Print String(Len(LINETEMPLATE), "-")
line = LINETEMPLATE: Mid(line, 6) = "Settings": Mid(line, 23) = "Old": Mid(line, 34) = "New": Debug.Print line
line = LINETEMPLATE: Mid(line, 2) = "Calculation": Mid(line, 21) = .Calculation: Mid(line, 33) = values(CALC): Debug.Print line
line = LINETEMPLATE: Mid(line, 2) = "ScreenUpdating": Mid(line, 21) = .ScreenUpdating: Mid(line, 33) = values(UPDATING): Debug.Print line
line = LINETEMPLATE: Mid(line, 2) = "EnableEvents": Mid(line, 21) = .EnableEvents: Mid(line, 33) = values(EVENTS): Debug.Print line
Debug.Print String(Len(LINETEMPLATE), "-")
End If
.Calculation = values(CALC)
.ScreenUpdating = values(UPDATING)
.EnableEvents = values(EVENTS)
Else
If Len(Label) > 0 Then
Debug.Print Label; ": Started "; Format(Now, "hh:mm:ss")
Debug.Print String(Len(LINETEMPLATE), "-")
line = LINETEMPLATE: Mid(line, 6) = "Settings": Mid(line, 21) = "Original": Debug.Print line
line = LINETEMPLATE: Mid(line, 2) = "Calculation": Mid(line, 22) = .Calculation: Debug.Print line
line = LINETEMPLATE: Mid(line, 2) = "ScreenUpdating": Mid(line, 22) = .ScreenUpdating: Debug.Print line
line = LINETEMPLATE: Mid(line, 2) = "EnableEvents": Mid(line, 22) = .EnableEvents: Debug.Print line
Debug.Print String(Len(LINETEMPLATE), "-")
End If
settings.Push Array(CDbl(Timer), .Calculation, .ScreenUpdating, .EnableEvents)
End If
End With
End Sub
Usage
Sub Demo1()
With Application
SaveAppState Restore:=False, Label:="Start Demo1"
.ScreenUpdating = False: .Calculation = xlCalculationManual: .EnableEvents = False
.Wait (Now + TimeValue("0:00:02"))
SaveAppState Restore:=True, Label:="End Demo1"
End With
End Sub
Immediate Window Log
Start Demo1: Started 03:18:37 ----------------------------------------- | Settings | Original | | |Calculation | -4105 | | |ScreenUpdating | True | | |EnableEvents | True | | ----------------------------------------- End Demo1-> Execution Time: 00:00:01 ----------------------------------------- | Settings | Old | New | |Calculation | -4135 | -4105 | |ScreenUpdating | False | True | |EnableEvents | False | True | -----------------------------------------