Close a workbook at a specific time - vba

I have looked everywhere to get an answer to this and can't figure it out. I'm looking for a way to close and save a workbook automatically at midnight every night because it has important information that people keep losing because they don't save it. I've got the closing and saving part down, if I manually click the X to close Excel, but I can't get it to do it at a predetermined time. I've used Application.OnTime without any success. Here is the code that I'm using to close and save the workbook. I just need the code to have it run at midnight if the workbook is still open.
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Sheet1.Activate
Sheet1.Range("AB18:AD18").Select
Selection.ClearContents
ActiveWorkbook.Save
End Sub

I had trouble referencing the Worksheet .CodeName property but not with the Worksheet .Name property.
Module1 code sheet
Option Explicit
Sub ScheduleClose()
Application.OnTime Now + TimeSerial(0, 0, 4), "Kamikaze"
End Sub
Sub Kamikaze()
Debug.Print "boom"
Application.DisplayAlerts = False
With ThisWorkbook
.Worksheets("Sheet1").Activate
.Worksheets("Sheet1").Range("AB18:AD18").ClearContents
.Save
.Close savechanges:=False
End With
End Sub
Of course, the workbook must be already saved (i.e. not Book1) since no name is provided but a Workbook.SaveAs method could be used if the workbook habitually is not already saved.
This will also leave 'orphaned' VBA projects within the Excel application 'instance'. VBA cannot kill itself as it has to be running to complete the command.

Related

Why does Macro Enabled Excel Workbook kill UserForm in another Macro Enabled Excel Workbook when closed?

Scenario:
User is running a macro-enabled Excel Workbook. User clicks a link on a UserForm that launches another macro-enabled Excel Workbook that also has user forms. The second Workbook is then closed using the following code:
Private Sub btnExit_Click()
'Check if other Excel files are open before quitting the application
If Workbooks.Count = 1 Then
Application.Quit
Else
ThisWorkbook.Close
End If
End Sub
and...
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Dim intResponse As Integer
If MsgBox("Are you sure you would like to exit the calculator?", vbYesNo + vbQuestion) = vbNo Then
Cancel = True
End If
ThisWorkbook.Saved = True
End Sub
Upon closing of the second Workbook (ThisWorkbook.Close), the original Workbook remains open, however, the vbModeless UserForm that was previously visible has been killed. It might also be pertinent to mention that the second Workbook's forms are Modal.
If the second Workbook is closed using the Excel exit button in the upper right corner, this problem does not occur.
Does anyone know why this is happening? Is it because code execution for Excel.Application is ceased when the Workbook is closed programmatically? Could it be due to the difference in form modes? Is there a work-around other than putting a button on the original Workbook in a Sheet to re-launch the UserForm?
Many thanks for any assistance with this issue!!
CiViCChiC79
This is what is happening:
Assuming that both workbooks are in the same Excel Application, Application.Quit would close them both.
ThisWorkbook.Close will close the workbook, in which the code is present.
Thus, concerning your question - you are not closing the second workbook with ThisWorkbook.Close, but you are closing the workbook, which has the btnExit button, on which you have clicked. I guess that both the workbooks are quite similar and you are mistaken which one you are closing?
Changing the second Workbook's form containing the Exit button to vbModeless fixed the problem!!!! Yay!!!!!

Passing VBA execution to another workbook

I though there would be many answers to this question, but didn't find any obvious one.
Is it possible to pass VBA execution to another workbook ?
What I'd like to do is : pass execution from ThisWorbook (WbA) to another workbook (WbB, ie make WbB the new ThisWorkook), then close Wb A.
Edit
What I'm actually trying to do...
I'm looking for a way to update a workbook I'm distributing to a few people and which contains 20 sheets + 10 code modules + 10 userforms.
My current solution
Every time the workbook (WbA) is open, the code will first check if a newer version is available on a network directory. If so, the macro propose to the user to automatically download/save the new workbook (WbB) and export user data from WbA.
But, after that, I'm forced to tell the user it can now closed WbA and open WbB and import the data back in WbB.
I'd rather automate the close WbA / open WbB / import data back too.
You can do this by using Application.OnTime:
Code in WbA.xlsm:
Sub Macro1()
Application.OnTime Now(), "WbB.xlsm!Macro2"
ThisWorkbook.Close
End Sub
Code in WbB.xlsm:
Sub Macro2()
MsgBox ThisWorkbook.Name & " says hello"
End Sub
Because Macro2 is being called by the Application via a scheduled task, it no longer needs WbA to be open.
Note: WbB.xlsm does not even need to be open when Macro1 is run, providing you fully qualify the workbook name:
Code in WbA.xlsm:
Sub Macro1()
Application.OnTime Now(), "'C:\Temp\SO_Test\WbB.xlsm'!Macro2"
ThisWorkbook.Close
End Sub
Code in WbB.xlsm:
Sub Macro2()
MsgBox ThisWorkbook.Name & " says hello"
End Sub
Assuming WbA.xlsm was the only workbook open in Excel when Macro1 was run, you might actually see an empty Excel for a very short time before WbB.xlsm is opened and execution continues.
You just have to leave workbook A open while the code runs, the last line of code you need to execute should be
WorkbookA.Close

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.

Shared Excel Document

I've got a Excel documents with macros with input and update forms for information, the document is used as a monthly database where you can run reports from as well.
The issue is that when using the document sometimes the input and update options are used at the same time causing information loss. Both the input and output save at the end of the macro to minimise the losses, but I was wondering if there is anyway of checking at runtime if there is a macro being use by another user and if so delay the next macro run until the other user is finished?
There is one way I can think of. Logically it should work. However I have not tested it.
Create a temp sheet and hide it
When anyone runs a macro, check if cell A1 of that sheet is empty or not
If it is empty then run the macro
Before running the macro, write to that cell and once the macro is run, clear the contents of the other cell
Sandwich your macro code as mentioned below
Code
Sub Sample()
Dim ws As Worksheet
ThisWorkbook.Save
Doevents
Set ws = ThisWorkbook.Sheets("HiddenSheetName")
If Len(Trim(ws.Range("A1").Value)) = 0 Then
ws.Range("A1").Value = "Macro Starts"
ThisWorkbook.Save
Doevents
'
'~~> Rest of your code goes here
'
ws.Range("A1").ClearContents
ThisWorkbook.Save
Doevents
Else
MsgBox "Please try after some time. There is a macro running... Blah Blah"
End If
End Sub
CAUTION: Once the code runs, you cannot undo the changes since the code save the file programatically. The changes are permanent. In a file which is not shared, you can undo by closing the file without saving and re-opening it.

Application.Calculation depending on workbook

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