Application.Calculation depending on workbook - vba

I'm managing a workbook with more than 200 000 formulas (some really complicated array formulas) which means that I can't let Excel automatically calculate all the cells every time I click somewhere (it takes around 8 hours to calculate everything).
Instead of that, the calculation is set to manual and I have the following VBA code executed when Calculation.xlsm is opened:
With Application
.CalculateBeforeSave = False
.Calculation = xlCalculationManual
End With
I use custom buttons to calculate only some parts of the 200k cells when needed.
I noticed that Excel does keep track of that setting in each workbooks, which means that if I open my Calculation.xlsm, Excel remembers that the calculation is set to manual. If I open my Values.xlsx, Excel does remember that the calculation is set to automatic. This was before I tried to copy values from Calculation.xlsm to Values.xlsx.
Now, because I'm using VBA in Calculation.xlsm to copy values to Values.xlsx, Excel does apply the Application.Calculation setting to that workbook too, which means that if I open it with a new instance of Excel, the calculation will still be set to manually.
If I add a Application.Calculation = xlCalculationAutomatic before closing the Values.xlsx with VBA in my Calculation.xlsm workbook, it will work, but Excel will also start to compute the 200k cells in my Calculation.xlsm workbook, which I obviously don't want.
So my question is about how to actually set the calculation of Excel based on a specific workbook instead of with the Application object. This is based on the fact that Excel does keep track of that setting depending on which workbook is opened (you can just do the test and create 2 different .xlsx files, one with the calculation enabled and the other with the calculation disabled and Excel will remember these settings).
I know I could use the Worksheets.Range.Calculate method to calculate my Values.xlsx workbook before closing it, but the calculation will still be set to manual if I open it in a new instance of Excel after that.
EDIT 3:20pm: Not sure if I was clear enough, English isn't my native language. In short, I have Calculation.xlsm with VBA and Calculation set to manual. I have Values.xlsx with no VBA and Calculation set to automatic. If I open Values.xlsx with the following VBA code in Calculation.xlsm, Excel will automatically convert my Values.xlsx workbook to manual calculations.
Calculation.xlsm code:
Private Sub Workbook_Open()
With Application
.CalculateBeforeSave = False
.Calculation = xlCalculationManual
End With
End Sub
Sub someFunction()
Set WB = Application.Workbooks.Open("Values.xlsx")
Set WBws = WB.Sheets("mySheet")
DoEvents
wb.Save
WB.Close
End Sub
After the execution of someFunction(), Values.xlsx calculation is set to manual. That's the problem. I would like it to stay on automatic (and I can't add VBA to that file, it must be all done from Calculation.xlsm like above).
EDIT 3:40pm: Could I just have my big workbook with Application.Calculation set to manual, put all the data I need in the clipboard (I only need the values, not the formulas), close it (will the VBA still continue to execute even if I close the workbook from which it is executed?), set Application.Calculation to Auto (since there is no open workbook), then open the destination workbook to paste the values (will Excel still keep the data in the clipboard since the other workbook is closed?), then save and close that workbook, set back the calculation to manual (no workbook opened) and reopen the original workbook from which the code was executed?

One way to do this would be to create a new instance of Excel. While this is probably slower, and might be more difficult to work with in cases where you don't close the book/application within the function, but for simple case like your example, it may be easiest to implement:
Sub someFunction()
Dim newExcel as Excel.Application
Set newExcel = CreateObject("Excel.Application")
Set WB = newExcel.Workbooks.Open("Values.xlsx")
Set WBws = WB.Sheets("mySheet")
DoEvents
wb.Save
WB.Close
newExcel.Quit
Set newExcel = Nothing
End Sub
The Application.Calculation property is relative to that instance of the application, not other instances.
Alternatively, you can use an application-level event handler. I suspect this might be faster but I have not tested it for speed.
Modified slightly from this very similar question (which also asks about conditionally disabling an Application-level property).
If:
I was just worrying about if the code would still be executed if I close the workbook from which it is launched
Then just use the normal Workbook_BeforeClose event handler to restore the desired Application.Calculation property (for the entire application/all other open workbooks).
The rest of the answer:
Create an application-level event handler, create a class module named cEventClass and put this code in it:
Public WithEvents appevent As Application
Dim ret
Private Sub appevent_WorkbookActivate(ByVal wb As Workbook)
Call ToggleCalculation(wb, ret)
End Sub
Use the following in a standard module named mod_Caclulate:
Option Explicit
Public XLEvents As New cEventClass
Sub SetEventHandler()
If XLEvents.appevent Is Nothing Then
Set XLEvents.appevent = Application
End If
End Sub
Sub ToggleCalculation(wb As Workbook, Optional ret)
If wb.Name = ThisWorkbook.Name Then
ret = xlCalculationManual
Else
ret = xlCalculationAutomatic
End If
Application.Calculation = ret
End Sub
Put this in the Workbook_Open event handler of the workbook which you always want to be manual calculation:
Option Explicit
Private Sub Workbook_Open()
'Create the event handler when the workbook opens
Call mod_Caclulate.SetEventHandler
Call mod_Caclulate.ToggleCalculation(Me)
End Sub
This will create the event handler only when the specific workbook is opened, and the handler will toggle the Calculation property whenever you switch views to a different workbook.
Note: If you "end" run-time or do anything while debugging which would cause state loss, you will lose the event handler. This can always be restored by calling the Workbook_Open procedure, so an additional safeguard might be to add this also in the ThisWorkbook code module:
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
' Additional safeguard in case state loss has killed the event handler:
' use some workbook-level events to re-instantiate the event handler
Call Workbook_Open
End Sub

Related

Workbook.Activate method acting strangely

I have an Excel add-in running a procedure that displays an OKOnly MsgBox if a certain criteria is not met, and attempts to close a userform, activate a specific workbook, and terminate code execution. In the UserForm_Terminate() event I have:
Private Sub UserForm_Terminate()
Debug.Print ActiveWorkbook.Name
Application.ScreenUpdating = True
wbk.Activate
sht.Activate
Debug.Print ActiveWorkbook.Name
End
End Sub
If I begin running the procedure with a new blank workbook active, that workbook is still the active workbook when code terminates, but both print statements above indicate that the target wbk is actually active. However if I use a breakpoint and step through wbk.Activate, the target wbk is activated as expected. The workbook and worksheet objects are both available and there is no error. Any ideas why Workbook.Activate is not behaving as expected during execution? I expected turning screenupdating on would solve my issue but no dice.
#barX He's not relying on ActiceWorkbook for code, he's just checking it to see if it's working ....
BTW Welcome to the Excel 2013 SDI bug!
Maybe following the proper way of initializing/using a Userform will help. See Mathieu's RubberDuck article on Userform.Show
On a related note, maybe changing the Parent Window Handle for the form before closing might work as well. See Keeping Userforms On Top Of SDI Windows In Excel 2013 And Up
I'm not sure if the SDI bug is my issue but it did lead me to an answer (thanks #Profex). I was not able to reproduce that bug, and there is also another path in my procedure that ends with the same Userform_Terminate() event and does reactivate the target wbk, though I can't determine what is causing the differing functionality. Nonetheless, one solution to that issue was to hide and then show the active window and that suffices in this case, though probably not ideal in many situations:
Private Sub UserForm_Terminate()
Dim win As Window
wbk.Activate
sht.Activate
Set win = Application.ActiveWindow
win.visible = False
win.visible = True
End
End Sub

How to make vba code to run auto-close only on active workbook?

Here's the scenario. I have multiple excel workbooks that copy and paste data among each other. So the macro works to open.copy.close from one workbook then open.paste.close to another. I am working on creating a function to auto run macro when file is closed.
Here's the issue, when I click macro button in workbook 1, it is supposed to open.copy.close from workbook 2. However, because of the auto run when file is closed function in workbook 2, an error will occur (2 macros cannot run at the same time)Any solution for this? I am looking for a solution to only auto run macro when file is closed IF IT IS AN ACTIVE WORKBOOK. Here is what I have now:
Workbook 1
Sub workbook_beforeclose(cancel As Boolean)
Application.Run "Sheet1.UpdateYellowTabs_Click"
End Sub
Workbook 2
Sub Workbook_BeforeClose(Cancel As Boolean)
Workbook.BeforeClose
Application.Run "Sheet12.UpdateGreen_Click"
End Sub
How do I code it in the workbook code to only make this run only when it's active/closed by a human user and not when open/close by macro?
Thanks!
Well I am not sure to understand your final goal from this, but I can answer the "technical" question. Technically, if you want to check if a given workbook is active: If Application.ActiveWorkbook Is Me,
Private Sub Workbook_BeforeClose(Cancel As Boolean)
If Not Application.ActiveWorkbook Is Me Then Exit Sub ' <-- add this test
''''''''''''''''''''''''''
' Whatever code goes here
''''''''''''''''''''''''''
End Sub
EDIT
But problem is that invoking wb2.close will make the workbook wb2 the "active" one during the execution of the macro. So this method won't work. Another method is to disable events before closing the workbook; so that the event Workbook_BeforeClose will not fire. After that, you can enable events back if needed. The code looks like this:
' ... open wb2 and do the work with it
Application.EnableEvents = False
wb2.Close False
Application.EnableEvents = True
notice, if you had already disabled events at the beginning of the current macro, which is usually recommended, then this additional code wouldn't be needed.

Excel 2003 - Workbook_Activate bug for ActiveSheet.Calculate

Context:
Excel 2003, Windows 7 Professional SP1
Working on workbook A, which contains custom functions.
Workbook B is opened or edited
Back to workbook A, which Workbook_Activate sub is the following:
Private Sub Workbook_Activate
ActiveSheet.Calculate
End Sub
This procedure should force the calculation of custom functions of workbook A, which cells display #VALUE during the edition of workbook B.
Below are the bugs I get on my computer, I hope you can help me with them!
When the workbook (not the whole Excel window) IS NOT in state ActiveWindow.WindowState = xlNormal (i.e. it is full screen or minimized)
OR:
When the workbook IS in state ActiveWindow.WindowState = xlNormal and the workbook has a width > 725 pixels (!)
THEN:
The function Workbook_Activate is fired all the way (good thing).
The function ActiveSheet.Calculate is not fired (or without effect).
Breakpoints don't stop the code.
You can't run any code that edits cells, or run Application.CalculateFull, without getting a display bug such as entire ranges where the text or border vanish until the ranges have been selected.
PROBLEM SOLVING:
In procedure Workbook_Activate, if we add ActiveWindow.WindowState = xlNormal before ActiveSheet.Calculate:
When the workbook IS NOT in state ActiveWindow.WindowState = xlNormal and the Excel window IS maximized
ONLY in this case, the bugs remain. It's the only case where function ActiveSheet.Calculate is not fired. ActiveWindowmethods don't fire either.
Obs.: Application.EnableEvents = False doesn't change anything. (It's probably not even triggered.)
Thank you!
I found a simple solution to avoid the bug.
Private Sub Workbook_Deactivate()
Application.Calculation = xlCalculationManual
End Sub
Private Sub Workbook_Activate()
Application.Calculation = xlCalculationAutomatic
End Sub
[Edit]
... But it was not acceptable, because the calculation was set to "Manual" by default on any other opened workbook.
The best solution is here: Excel 2003 - Display bug with conditional formatting

Closing a userform that is in workbook A from workbook B

I'm new to VBA so there might be a simple answer to this question but if there is I sure haven't found it. What I am doing is copying data from several workbooks into one master workbook. I have writen the code for this and it works fine. The only problem is the workbooks where I'm retriving the data have userforms that automatically initiate when the workbook is accesed. This means that when I run my code to copy the data it hangs at each userform and wont continue until I've physically closed each userform. So my question is: Is there a way to remotely close the userforms in the raw data workbooks from my master workbook VBA code? Thanks in advance.
to close all userforms, (if you want a specific one , change my code)
sub Close_Userforms()
Dim Form as VBA.Userform 'if not work change to Object
For each Form in VBA.Userform
'can add a condition, like : if Form.name ="Whatever" then
unload Form 'if you don't want to lose the data from the userforms, Form.Hide, and later re-loop and Form.Show
next Form
edit : can also if Typename (Form)="Whatever" then , for the condition
Assuming you mean that the forms pop up when you open the workbooks, disable events before doing so:
Application.Enableevents = False
Workbooks.Open ...
Application.Enableevents = True
for example.
I would suggest trying
Application.EnableEvents = False
Further reading.
Short description: All events (Workbook_Open, Workbook_BeforeSave etc), that usually fires upon opening or closing a workbook, will be ignored.
I have written the following functions to make all macros a bit simpler (and faster). Simply place these functions in a regular module.
Public Function CalcOff()
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Application.EnableEvents = False
End Function
Public Function CalcOn()
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
Application.EnableEvents = True
End Function
Begin your macro with:
CalcOff
And then end your macro with:
CalcOn
Remember that you need to put "CalcOn" in all places that exits the running macro.
Disabling ScreenUpdating makes the code "run in background" (nothing will be displayed).
Setting Calculation to manual improves the speed of the code, since no calculations will be made when changing data. But it's very important to exit all macros with "CalcOn", otherwise your sheet won't calculate (and that's not funny), and it will look like Excel has frozen (since ScreenUpdating would still be turned off).
However, if you by any chance happen to break a running code without exiting it the proper way (running "CalcOn"), simply close the Excel application and reopen it. Or run a macro that ends with the "CalcOn" code. Or create a new macro with that simple line.

How do I disable and enable macros on the fly?

I would like to test an Excel VBA app I made.
However the VBA code messes around with the visibility of cells and that's a pest when editing the sheet.
Is there an option is enable and disable macro's on the fly without having to
Close the sheet
Change the macro settings
Reopen the sheet
Close the sheet
Change the macro settings.
etc.
As far as I know, you can't enable / disable macros from an opened workbook on the fly.
Yet, you shouldn't have to because macros are only triggered thanks to a user click.
The only case I would see is for the Event Procedures (Worksheet_Change or else).
You could then create procedures to activate / deactivate events and call them from buttons in your worksbook:
Sub enableEvents()
Application.EnableEvents = True
End Sub
Sub disableEvents()
Application.EnableEvents = False
End Sub
You can also try these tips from Chris Pearson website using global vars you would change depending on your needs:
Public AbortChangeEvent As Boolean
And check if afterwards:
Private Sub Worksheet_Change(ByVal Target As Range)
If AbortChangeEvent = True Then
Exit Sub
End If
'
' rest of code here
'
End Sub
To disable macros on the fly, use "Application.EnableEvents = False" via the Immediate window in the VBA editor (and "Application.EnableEvents = True" to turn them back on).
You can also hold down SHIFT when you open a document to disable macros.
As of Excel 2010* there's actually an option on the "Developer" tab which allows you to disable macros as easy as ABC. Design Mode lets you do just that!
*Maybe even earlier versions.
I often fire Macros when opening a workbook but sometimes I don't want the Macro to fire so I can work on the code. So what I put a Macro in a separate workbook that does the following:
Disables macros
Opens the workbook in question
Enables macros
Then closes the original workbook
Leaves the second workbook open
Here's the code:
Sub OpenClose()
'Opens Workbook below with Macors disabled
'After it is open Macros are enabled
'This Workbook then closes without saving changes, leaving only the workbook below open
'************************************************************
'User only needs to change the workbook name on the next line
WorkbookToOpenNoMacros = "Gaby.xlsm"
'************************************************************
Dim wb As Workbook
Set wb = Application.ThisWorkbook
Application.EnableEvents = False
Application.Workbooks.Open (ActiveWorkbook.Path & "\" & WorkbookToOpenNoMacros)
Application.EnableEvents = True
wb.Saved = True
wb.Close SaveChanges:=False
End Sub
I have Office 2013 and I found a button on the VBA window that did this for me very easily.
There's a play, pause and stop button on the toolbar. In Excel, they are actually called, Run Macro, Break and Reset.
Click the Break button (pause) and any running macros should stop running. I only tested it on one macro but seems reasonable to presume that this will work in general.
Also, I believe those buttons were there for many versions of Excel, so it's worth checking earlier versions.