I have a code which checks "before save" event whether user fill mandatory cells.
When I tried to add additional line for give file to an automated name, code freezes. Yet create the file. Below you can find my code, most of the code is just checking the cells, but I'm not sure the reason of error, so I'm adding all of it in case there's something I missed.
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Dim message As String
Dim say As Long
say = Application.WorksheetFunction.CountA(Worksheets("ACC REQ").Range("C:C"))
If Application.WorksheetFunction.CountA(Worksheets("ACC REQ").Range("D:D")) <> say Then
message = Range("D1").Value & vbCrLf
End If
If Application.WorksheetFunction.CountA(Worksheets("ACC REQ").Range("F:F")) <> say Then
message = message & Range("F1").Value & vbCrLf
End If
If Application.WorksheetFunction.CountA(Worksheets("ACC REQ").Range("G:G")) <> say Then
message = message & Range("G1").Value & vbCrLf
End If
If Application.WorksheetFunction.CountA(Worksheets("ACC REQ").Range("H:H")) <> say Then
message = message & Range("H1").Value & vbCrLf
End If
If Application.WorksheetFunction.CountA(Worksheets("ACC REQ").Range("I:I")) <> say Then
message = message & Range("I1").Value & vbCrLf
End If
If Application.WorksheetFunction.CountA(Worksheets("ACC REQ").Range("J:J")) <> say Then
message = message & Range("J1").Value & vbCrLf
End If
If Application.WorksheetFunction.CountA(Worksheets("ACC REQ").Range("K:K")) <> say Then
message = message & Range("K1").Value & vbCrLf
End If
If Application.WorksheetFunction.CountA(Worksheets("ACC REQ").Range("M:M")) <> say Then
message = message & Range("M1").Value & vbCrLf
End If
If Application.WorksheetFunction.CountA(Worksheets("ACC REQ").Range("N:N")) <> say Then
message = message & Range("N1").Value & vbCrLf
End If
If Application.WorksheetFunction.CountA(Worksheets("ACC REQ").Range("Q:Q")) <> say Then
message = message & Range("Q1").Value & vbCrLf
End If
If Application.WorksheetFunction.CountA(Worksheets("ACC REQ").Range("R:R")) <> say Then
message = message & Range("R1").Value & vbCrLf
End If
If Application.WorksheetFunction.CountA(Worksheets("ACC REQ").Range("AU:AU")) <> say Then
message = message & Range("AU1").Value & vbCrLf
End If
If message <> "" Then
MsgBox "" & message & vbCrLf & "Can’t Save with Empty Cells!!"
Cancel = True
End If
ThisFile = Format(Now(), "yyyy-mm-dd") & "__" & "ACC__" & Range("H2").Value & "__" & "CR"
ActiveWorkbook.SaveAs Filename:=ThisFile & ".xlsx"
End Sub
regards
Solution:
Put Cancel=True at the end of the procedure to keep Excel from freezing due to an infinite loop.
When you save the file, the Workbook_BeforeSave event runs *before Excel saves the file** like it normally would.
This can be prevented with Cancel=True, which is necessary in this case since you want to SaveAs it yourself.
Without Cancel=True, your SaveAs is triggered the Workbook_BeforeSave event again, where you SaveAs again which triggers the Workbook_BeforeSave event again....etc....
Alternative (more compressed):
Your code should work with the change above, but below is a way to compress the code further by removing repetition. (See also, how to create a Minimal, Complete, and Verifiable example.)
The size reduction is because of the use of With..End With and looping through a static array to avoid repeating the same code.
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Dim msg As String, say As Long, ws As Worksheet, col
Set ws = Worksheets("ACC REQ")
With Application.WorksheetFunction
say = .CountA(ws.Columns("C"))
For Each col In Array("D","F","G","H","I","J","K","M","N","Q","R","AU")
If .CountA(ws.Columns(col))<>say Then msg=msg & Range(col & "1") & vbCrLf
Next col
Cancel = True 'we don't need Excel to save it
End With
If msg <> "" Then
MsgBox msg, , "Can't Save with Empty Cells!": Exit Sub
End If
ActiveWorkbook.SaveAs Format(Now(), "yyyy-mm-dd") _
& "__ACC__" & Range("H2") & "__CR.xlsx"
End Sub
This one took me a minute but I know what's the problem! You have an Event that is called BeforSave in which you save. Which means that you have the Event within it self. This causes an infinite loop.
Do this:
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Application.EnableEvents = False
Dim message As String
Dim say As Long
Dim ThisFile As String
Dim Path As String
'.. Check stuff ..
Path = "C:\YourPath\YourFolder\"
ThisFile = Format(Now(), "yyyy-mm-dd") & "__" & "ACC__" & Range("H2").Value & "__" & "CR"
ThisWorkbook.SaveAs Filename:=ThisFile & ".xlsm"
Application.EnableEvents = True
Cancel = True
End Sub
This should solve your problems as it disables the events for the duration of the actual saving. Make sure that you have the Application.EnableEvents=True otherwise it will not fire at all.
Related
Why does the userform (a barcode) not show up with this code (only timer is running):
Sub Barcode_Show()
'show barcode form
On Error GoTo Errorhandler
Application.OnTime Now, "Barcode_Close"
DoEvents
Load form_Barcode
With form_Barcode
.Barcode128 = rngTarget.Value
**If ActiveSheet.CodeName = wsPastorer Then
.BarcodeLabel = "P" & TankCell.Value & " - " & ProduktCell.Value
Else
.BarcodeLabel = "T" & TankCell.Value & " - " & ProduktCell.Value
End If**
.Show
End With
Exit Sub
Errorhandler:
Call Barcode_Close
End Sub
When I change it to
DoEvents
Load form_Barcode
With form_Barcode
.Barcode128 = rngTarget.Value
.BarcodeLabel = "T" & TankCell.Value & " - " & ProduktCell.Value
.Show
End With
it will work.
I tried to have some other code in that place before, and it didn't work either. Why is it not possible and how to write the code so the form will show?
I am trying to figure out how to avoid the multiple msgboxes which appear when I run my code:
Public Function IsItGood(aWord As Variant) As Boolean
Dim s As String
s = "|"
tmp = s & aWord & s
patern = ""
For i = 1 To 100
patern = patern & s & i
Next i
For i = 1 To 10
patern = patern & s & "C" & i
Next i
patern = patern & s & "merge|complete framed|width|border left|border right" & s
If InStr(1, patern, tmp) > 0 Then
IsItGood = True
Else
IsItGood = False
End If
End Function
Above is the function which is used in the below worksheet_change:
Sub Worksheet_Change(ByVal Target As Range)
Dim BigS As String
Dim arr As Variant
Dim a As Variant
If Intersect(Range("G3:G19"), Target) Is Nothing Then Exit Sub
arr = Split(Target, " ")
If IsItGood(a) Then
MsgBox (" In row" + Target.Address(0, 0)) & vbCrLf & a & vbCrLf + "are ok"
Else
MsgBox Target.Address(0, 0) & vbCrLf & a & vbCrLf & "has bad stuff"
Application.Undo
End If
End Sub
The first "for" loops the 100 integers and the second from C1 to C10 and the msgbox is repeated for each splitted string.Is there a way to prevent the multiple msgboxes so only one msgbox to appear at a time. And also an "out of stack space" error appears because of the recursion.
Set at the beginning: Application.EnableEvents = False and set it to True at the end. Recursion occurs, because you are calling macro on change event of workbook, which also generates this event, thus the method is calling itself, thus recursion.
I am trying to create a macro that, upon save, asks the user if the file they are working is the final version. If it is, I would like to save a copy of that file in a different destination. It also creates an indicator with the username and date saved of the final copy so that if a user tries to create ANOTHER final copy, it asks them if they would like to overwrite the version created by [username] on [date].
I decided to use AfterSave as opposed to BeforeSave, as I would like the user to have the option of choosing between Save and SaveAs before the macro runs.
The issue that I am having is that if the user indicates that it is the final version, a copy is saved, triggering the AfterSave event. Is there a line of code I can add that would stop the AfterSave event after the file copy is saved?
Here is my current code.
Private Sub Workbook_AfterSave(ByVal Success As Boolean)
If Success Then
Call YesNoMessageBox
End If
End Sub
'Saves copy of tool if final version
Sub YesNoMessageBox()
Dim Answer1 As String
Dim MyNote1 As String
Dim fileName As String
Dim dlgOpen As FileDialog
Dim MyYear
Dim FilePath
Dim Answer2 As String
Dim MyNote2 As String
MyNote1 = "Is this the FINAL version?"
'Display MessageBox
Answer1 = MsgBox(MyNote1, vbQuestion + vbYesNo, "???")
If Answer1 = vbYes Then
If Not Worksheets("Data Input").Range("M2") = vbNullString Then
MyNote2 = "There is already a version saved by " & Worksheets("Data Input").Range("M2") & " on " & Worksheets("Data Input").Range("M3") & "." & vbNewLine & "Would you like to overwrite it?"
Answer2 = MsgBox(MyNote2, vbQuestion + vbYesNo, "???")
If Answer2 = vbYes Then
strUName = CreateObject("WScript.Network").UserName
Worksheets("Data Input").Range("M2") = strUName
Worksheets("Data Input").Range("M3") = Date
'Saves copy of tool in [folder name] folder
MyYear = Year(Worksheets("Data Input").Range("D13"))
ThisWorkbook.SaveAs fileName:="G:\[file path]" & Worksheets("Data Input").Range("D9") & " - " & Worksheets("Data Input").Range("D7") & " " & MyYear & ".xlsm", FileFormat:=xlOpenXMLWorkbookMacroEnabled
MsgBox "Copy of tool saved in" & Application.ThisWorkbook.Path
End If
Else
strUName = CreateObject("WScript.Network").UserName
Worksheets("Data Input").Range("M2") = strUName
Worksheets("Data Input").Range("M3") = Date
'Saves copy of tool in [folder name]folder
MyYear = Year(Worksheets("Data Input").Range("D13"))
ThisWorkbook.SaveAs fileName:="G:\[File Path]\" & Worksheets("Data Input").Range("D9") & " - " & Worksheets("Data Input").Range("D7") & " " & MyYear & ".xlsm", FileFormat:=xlOpenXMLWorkbookMacroEnabled
MsgBox "Copy of tool saved in" & Application.ThisWorkbook.Path
End If
End If
End Sub
Disable events before the SaveAs but don't forget to enable again after:
Application.EnableEvents = False
ThisWorkbook.SaveAs fileName:="G:\[File Path]\" & Worksheets("Data Input").Range("D9") & " - " & Worksheets("Data Input").Range("D7") & " " & MyYear & ".xlsm", FileFormat:=xlOpenXMLWorkbookMacroEnabled
Application.EnableEvents = True
I'm using the following code to save an updated workbook.
Private Sub cmdSaveUpdatedWB_Click()
On Error GoTo Err_cmdSaveUpdatedWB_Click
gwbTarget.Activate <<<<<<<<<<<<<<<<<<<<<<<
Application.DisplayAlerts = False
gwbTarget.SaveAs txtUpdWorkbookName.Value, FileFormat:=xlOpenXMLWorkbookMacroEnabled
Application.DisplayAlerts = False
frmLoanWBMain.Show
gwbTarget.Close
Set gwbTarget = Nothing
gWBPath = ""
gWBName = ""
lblWorkbookSaved.Enabled = True
cmdUpdateAnotherWorkbook.Visible = True
Exit_cmdSaveUpdatedWB_Click:
Exit Sub
Err_cmdSaveUpdatedWB_Click:
MsgBox "The following error occurred inthe [cmdSaveUpdateWB_Click] event handler." & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & "Error descriptioin: " & Err.Description
Resume Exit_cmdSaveUpdatedWB_Click
End Sub
As noted in the title, the SaveAs operation fails. I've determined that the failure is a result of having the workbook to be saved losing the focus. I can step through the code and get the error. Once the error is generated, selecting Debug in the error message box and then pressing F5 to run the code will result in the workbook saving correctly. Placing Debug.Print statements before and after the Activate method of the worbook to be saved indicates that the active wokbook is the workbook containing the code and the form used to update the workbook. Placing a print statement in the Immediate wondow that prints the ActiveWorkbook.Name will result in printing the name of the workbook to be saved - gwbTarget.Name. Pressing F5 then runs the code correctly.
I have been unable to figure out why the workbook to be saved loses the focus. I placed delays, multiple activation statements, local variables to use for the workbookto be saved, and for the name of the workbook to be saved. Any help or ideas as to why this is happening and how to fix it will be greatly appreciated.
I did make some changes. The code is listed below...
Private Sub cmdSaveUpdatedWB_Click()
On Error GoTo Err_cmdSaveUpdatedWB_Click
Dim wbSave As Workbook
Set wbSave = gwbTarget
gwbTarget.Activate
Application.DisplayAlerts = False
''''''' gwbTarget.SaveAs txtUpdWorkbookName.Value, FileFormat:=xlOpenXMLWorkbookMacroEnabled
wbSave.SaveAs fileName:=txtUpdWorkbookName.Value, FileFormat:=xlOpenXMLWorkbookMacroEnabled
Application.DisplayAlerts = False
frmLoanWBMain.Show
gwbTarget.Close
Set gwbTarget = Nothing
gWBPath = ""
gWBName = ""
lblWorkbookSaved.Enabled = True
cmdUpdateAnotherWorkbook.Visible = True
Exit_cmdSaveUpdatedWB_Click:
Set wbSave = Nothing
Exit Sub
Err_cmdSaveUpdatedWB_Click:
MsgBox "The following error occurred inthe [cmdSaveUpdateWB_Click] event handler." & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & "Error descriptioin: " & Err.Description
Resume Exit_cmdSaveUpdatedWB_Click
End Sub
I've changed the code to more closely resemble the suggestion below. The listing is below, along with the variable definitions as they were upon entry into the program. The Excel code is running in a Citrix environment which may effect timing but shouldn't have any other effect on code execution.
I deleted the other code versions for brevity. The following code is what has worked. The key issue is that the workbook to be saved must be the active workbook when the SaveAs method is invoked.
Private Sub cmdSaveUpdatedWB_Click()
On Error GoTo Err_cmdSaveUpdatedWB_Click
Dim wbSave As Workbook
Dim wsActive As Worksheet
Dim sNWBName As String
Application.DisplayAlerts = False
sNWBName = txtUpdWorkbookName.Value
Set wbSave = gwbTarget
wbSave.Activate
Set wsActive = wbSave.ActiveSheet
wbSave.SaveAs fileName:=sNWBName, FileFormat:=xlOpenXMLWorkbookMacroEnabled
Application.DisplayAlerts = True
frmLoanWBMain.Show
gwbTarget.Close
Set gwbTarget = Nothing
gWBPath = ""
gWBName = ""
lblWorkbookSaved.Enabled = True
cmdUpdateAnotherWorkbook.Visible = True
Exit_cmdSaveUpdatedWB_Click:
Set wbSave = Nothing
Exit Sub
Err_cmdSaveUpdatedWB_Click:
Dim strErrMsg As String
strErrMsg = "Error Number: " & Err.Number & " Desc: " & Err.Description & vbCrLf & _
"Source:" & Err.Source & vbCrLf & _
"Updating Workbook: " & vbCrLf & " " & gwbTarget.Name & vbCrLf & _
"Selected Worksheet: " & gwsTrgSheet.Name & vbCrLf & _
"Active Workbook: " & vbCrLf & " " & ActiveWorkbook.Name & vbCrLf & _
"Worksheet: " & ActiveSheet.Name & vbCrLf & _
"Code Segment: cmdSaveUpdatedWB_Click event handler"
RecordErrorInfo strErrMsg
Resume Exit_cmdSaveUpdatedWB_Click
End Sub
Why don't you start with something like this
Private Sub cmdSaveUpdatedWB_Click()
Dim gwbTarget As Workbook
Set gwbTarget = Workbooks("workbook_name.xlsm") 'correct extension needed, workbook must be open
wb.SaveAs Filename:=gwbTarget.Path, FileFormat:=xlOpenXMLWorkbookMacroEnabled
MsgBox "Last saved: " & gwbTarget.BuiltinDocumentProperties("Last Save Time")
End Sub
Change one thing at a time to make it more like yours and hopefully it'll all work fine!
Update
As per the comments. If you are trying to open, update and close hundreds of workbooks. You can use this as a guide:
Sub ChangeWorkbooks()
Application.ScreenUpdating = False
Dim wbPaths As Range, wbSaveFilenames As Range
With Sheet1 'you will need to update this and the ranges below
Set wbPaths = .Range("A1:A650") 'including file extensions
Set wbSaveFilenames = .Range("B1:B650") 'including file extensions
End With
Dim i As Integer, totalBooks As Integer
Dim wbTemp As Workbook
totalBooks = wbPaths.Rows.Count
For i = 1 To totalBooks
Application.StatusBar = "Updating workbook " & i & " of " & totalBooks 'display statusbar message to user
Set wbTemp = Workbooks.Open(wbPaths.Cells(i, 1), False)
'make changes to wbTemp here
wbTemp.SaveAs wbSaveFilenames.Cells(i, 1)
wbTemp.Close
Next i
Set wbTemp = Nothing
Application.ScreenUpdating = True
Applicaton.StatusBar = False
End Sub
I am completely lost so any help will be greatly appreciated.
I am attempting to cancel 2 scheduled event that are triggered when the Workbook is opened, and repeated using the Application.Ontime method.
I know that to terminate the OnTime schedule loop, you must provide the exact time that it is scheduled to run and that having multiple Application.OnTime tasks requires multiple variables.
This is why I have set two Public variables (Header of the document below Options Explicit):
Dim dTime as Date
Dim dTime2 as Date
The scheduler use these Variables and everything works properly as the code runs every minute.
dTime's value is set inside the TaskTracker function to be:
dTime = Now() + TimeValue("00:01:00")
Application.OnTime dTime, "TaskTracker", , True
dTime2's value is set inside the Autoclear function to be:
dTime2 = Now() + TimeValue("00:01:00")
Application.OnTime dTime, "AutoClear", , True
Despite all this, I get a Method 'ONTIME' of Object'Application' error message when attempting to run the function at the end of the module:
Function AutoDeactivate()
Application.OnTime EarliestTime:=dTime, Procedure:="TaskTracker", _
Schedule:=False
Application.OnTime EarliestTime:=dTime2, Procedure:="AutoClear", _
Schedule:=False
End Function
This is where I absolutely do not get what is going wrong. Triggering the Debug brings me to the OnTime section of each procedure cancel attempt.
Below is the script that contains these elements. Hopefully this will give you guys some insight as to why these event can't be canceled.
Option Explicit
Dim dTime As Date
Dim dTime2 As Date
'------------------------------------------------------------
'This is what checks cells to define if an email notification has to be sent, and what the content of that email should be.
'------------------------------------------------------------
Function TaskTracker()
Dim FormulaCell As Range
Dim FormulaRange As Range
Dim NotSentMsg As String
Dim MyMsg As String
Dim SentMsg As String
Dim SendTo As String
Dim CCTo As String
Dim BCCTo As String
Dim MyLimit As Double
Dim MyLimit2 As Double
dTime = Now() + TimeValue("00:01:00")
NotSentMsg = "Not Sent"
SentMsg = "Sent"
SendTo = ThisWorkbook.Worksheets("Tasks").Range("D2")
CCTo = ThisWorkbook.Worksheets("Tasks").Range("E2")
BCCTo = ThisWorkbook.Worksheets("Tasks").Range("F2")
MyLimit = Date
MyLimit2 = ((Round(Now * 1440, 0) - 30) / 1440)
Set FormulaRange = ThisWorkbook.Worksheets("Tasks").Range("F5:F35")
On Error GoTo EndMacro:
For Each FormulaCell In FormulaRange.Cells
With FormulaCell
If DateValue(CDate(.Value)) = MyLimit Then
MyMsg = SentMsg
If .Offset(0, 1).Value = NotSentMsg Then
strTO = SendTo
strCC = CCTo
strBCC = BCCTo
strSub = "[Task Manager] Reminder that you need to: " & Cells(FormulaCell.Row, "B").Value
If Cells(FormulaCell.Row, "C").Value = "" Then
strBody = "Greetings, " & vbNewLine & vbNewLine & _
"Your task : " & Cells(FormulaCell.Row, "B").Value & " is nearing its Due Date: " & Cells(FormulaCell.Row, "F").Value & "." & vbNewLine & "A wise decision would be to complete this task before it expires!" & _
vbNewLine & vbNewLine & "Truly yours," & vbNewLine & "Task Manager"
Else
strBody = "Hello, " & vbNewLine & vbNewLine & _
"Your task : " & Cells(FormulaCell.Row, "B").Value & " with the mention: " & Cells(FormulaCell.Row, "C").Value & " is nearing its Due Date: " & Cells(FormulaCell.Row, "F").Value & "." & vbNewLine & "A wise decision would be to complete this task before it expires!" & _
vbNewLine & vbNewLine & "Truly yours," & vbNewLine & "Task Manager"
End If
If sendMail(strTO, strSub, strBody, strCC) = True Then MyMsg = SentMsg
End If
Else
MyMsg = NotSentMsg
End If
If .Value = MyLimit2 Then
MyMsg = NotSentMsg
End If
Application.EnableEvents = False
.Offset(0, 1).Value = MyMsg
Application.EnableEvents = True
End With
Next FormulaCell
ExitMacro:
Exit Function
EndMacro:
Application.EnableEvents = True
MsgBox "Some Error occurred." _
& vbLf & Err.Number _
& vbLf & Err.Description
Application.OnTime dTime, "TaskTracker", , True
End Function
'------------------------------------------------------------
'This is the function that clears the rows of Completed Tasks
'------------------------------------------------------------
Function AutoClear()
Dim i As Integer
dTime2 = Now() + TimeValue("00:01:00")
With Tasks
For i = 5 To 35
If .Cells(i, 4).Value Like "Done" And .Cells(i, 5).Value = "1" Then
.Cells(i, 1).ClearContents
.Cells(i, 2).ClearContents
.Cells(i, 3).ClearContents
.Cells(i, 5).ClearContents
.Cells(i, 6).ClearContents
.Cells(i, 4).Value = "Pending"
.Cells(i, 7).Value = "Not Sent"
End If
Next i
End With
Tasks.AutoFilter.ApplyFilter
Application.OnTime dTime2, "AutoClear", , True
End Function
'------------------------------------------------------------
'ThisWorkbook calls this to deactivate the Application.OnTime. This "should" prevent the Excel process from reoppening the worksheets.
'------------------------------------------------------------
Function AutoDeactivate()
On Error Resume Next
Application.OnTime EarliestTime:=dTime, Procedure:="TaskTracker", _
Schedule:=False
Application.OnTime EarliestTime:=dTime2, Procedure:="AutoClear", _
Schedule:=False
End Function
It looks like it was a setup mistake!
Option Explicit
Dim dTime As Date
Dim dTime2 As Date
Application.OnTime dTime, "TaskTracker", , True
Application.OnTime dTime2, "AutoClear", , True
With the AutoDeactivation function called when the workbook closes does work as intended!
Function AutoDeactivate()
On Error Resume Next
Application.OnTime EarliestTime:=dTime, Procedure:="TaskTracker", _
Schedule:=False
Application.OnTime EarliestTime:=dTime2, Procedure:="AutoClear", _
Schedule:=False
End Function
Workbook_BeforeClose:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Call AutoDeactivate
End Sub
What was happening is pretty idiotic. I had an issue with canceling the event at Work, so I took the Excel Sheet home and coded the fix found above. Yet, it still didn't work. Not because there was a mistake in it, but because I didn't have Outlook at home! :P
Not having the Outlook application prevented the event from being rescheduled after running once (resulting in an auto-dismissed ActiveX error message).
So as soon as I took this script back to work (where Outlook is installed) and everything worked properly :)
Marking this as resolved by myself haha.