Update: After more research, I found this duplicate question: Excel 2016 Workbook.BeforeClose event firing every other time bug. It seems I was using the wrong keywords, and that this is a bug, not a problem with my code. However, I cannot seem to download the version mentioned in the solution. I am running Windows 7 and using Microsoft Office 365 Pro Plus, and Office is stating that the most up to date version available is 16.0.6965.2105
I am trying to use the Workbook_BeforeClose event to test whether a checkbox is checked or not. If not, the user is prompted on whether they want to continue closing with out checking the box. If they choose "Yes," the sheet is cleared and the workbook is saved. If they choose, "No," the box is checked and, "Cancel" is set to true.
This works fine the first time the Workbook_BeforeClose event runs. However, the second time the sheet is closed, the standard Excel "Want to save your changes to..." dialogue box comes up and the Workbook_BeforeClose event does not fire. If I click cancel on the dialogue box and close the workbook a third time, the event fires. Something is being reset when, "Cancel," is clicked in the dialogue box, but I can't figure out what it is. My code is below:
Public Closing as Boolean
Sub Workbook_BeforeClose(Cancel As Boolean)
Debug.Print "Workbook_BeforeClose"
If Closing = True Then Exit Sub 'Closing is used as a switch to stop the event from looping on Application.ThisWorkbook.Close below
Closing = True
If Sheets(1).DraftCheckBox = False Then
If MsgBox("This file is not being saved as a draft. This workbook will be cleared if the draft box is not checked." & vbCr & vbCr & "Would you like to continue?", vbYesNo, "Warning") = vbYes Then
'If "Yes" selected
'Stuff happens here
Application.ThisWorkbook.Close savechanges:=True
Else
'If "No" selected
Sheets(1).DraftCheckBox = True
Cancel = True
End If
End If
Closing = False
End Sub
I know for a fact that Application.EnableEvents is set to True. I also know that the event itself is not firing because there is a stop on the very first line at "Debug.Print" This stop is activated after the first close and I can step through the code. It does not activate at all after the second close, either before or after the dialogue box. Is there anything that I should be doing to prevent the dialogue box from coming up and to force the Workbook_BeforeClose event every time the workbook is closed?
You are already closing the workbook you don't want to call close again, use Save instead.
You also don't need the extra variable if you use the code below. One big question remains...how is your user imitating the Close event? If by the red X then the code below should work. If by a button on your form what does that button code look like?
Note: This is untested code as I don't have your workbook.
Sub Workbook_BeforeClose(Cancel As Boolean)
If Sheets(1).DraftCheckBox = False Then
If MsgBox("This file is not being saved as a draft. " + _
"This workbook will be cleared if the draft box is not checked." _
& vbCr & vbCr & "Would you like to continue?", _
vbYesNo, "Warning") = vbYes Then
'If "Yes" selected
'Stuff happens here
Application.ThisWorkbook.Save
Cancel = False 'Just to be sure.
Else
'If "No" selected
Sheets(1).DraftCheckBox = True
Cancel = True
End If
End If
End Sub
HTH
Related
I have the following two codes on a button: The (first code) aims to submit value of option button into worksheet cell:
For Each FormControl In Me.Controls
'Check only OptionButtons
If TypeName(FormControl) = "OptionButton" Then
'Check the status of the OptionButton
If FormControl.Value = True Then
'Set a variable equal to the Caption of the selected OptionButton
OptionButtonValue = FormControl.Caption
'We found the selected OptionButton so exit the loop.
Exit For
End If
End If
Next
'Store input in the worksheet
Sheets("Answer Sheet").Range("E80").Value = OptionButtonValue
To ensure an option button is selected before proceeding to next form, i have
the 'following code (second code):
Dim cnt As Integer
For Each ctl In Me.Controls
If TypeName(ctl) = "OptionButton" Then
If ctl.Value = True Then cnt = cnt + 1
End If
Next ctl
If cnt = 0 Then MsgBox "Hello " & CStr(ThisWorkbook.Sheets("AccessReg").Range("D630").Value) & ", you
have not selected an answer! Please select an answer to proceed to next question. Thank you.",
vbInformation, "Please select an answer!" Else ScoreBoards.Show
Unload Me
MY CHALLENGES
Both codes above exists in my forms i.e. questions 1, 2, 3,...respectively, but can't seem to get the second code (that, which ensures an option button is selected before next form can be opened) to work by adding 'unload me' to the end of it, yet i want the form closed before proceeding to next. Adding 'unload me', pop-up the msgbox (which tells me to select an answer) but when i clicked okay on the msgbox, it closes the form (Question1) instead of returning me to same form to ensure an answer is clicked, then proceed to next form (Question2). However, when i remove the 'unload me', things work fine i.e. the msgbox popup when selection not made, returns to same form when okay on msgbox is clicked, and opens next form when selection made.
What i really want is: i want the second code above (which ensures an option button is selected before next form can be opened) to work as programmed and each form closed before proceeding to the next form.
Thank you in advance
PS:
The concept summary is:
On a userform (Question1), select an option button and submit the value to worksheet
Ensure an option button is selected:
if selected and button clicked, the next form(being Question2) should open
if not selected and button clicked, the msgbox (which tells me to select an answer), should popup
clicking okay on the msgbox, should return me to same form (Question1) so that i can select an option and proceed.
Try adapting your second code in the next way, please:
If cnt = 0 Then MsgBox "Hello " & _
CStr(ThisWorkbook.Sheets("AccessReg").Range("D630").Value) & ", you have not selected an answer! Please select an answer to proceed to next question. Thank you.", vbInformation, "Please select an answer!"
Exit For
Else
ScoreBoards.Show
Unload Me
End if
I will try to be as clear as possible in the description, so here goes nothing:
I have created a code in which the user selects his excel file and then the macro copies the Sheet from that file into my macro Workbook.
MyFile = Application.GetOpenFilename()
Workbooks.Open (MyFile)
ActiveSheet.Copy After:=wbook.Sheets(1)
ActiveSheet.Name = "Selected file"
Workbooks.Open (MyFile)
ActiveWorkbook.Close SaveChanges:=False
This is working, but what I realized is, that there might be cases where the selected file has multiple Sheets.
Is there a way to write the macro in which if my selected file has 1 sheet it runs the above code and if it has more than one sheet to let me select the sheet I want and then run the rest of the code?
Edit:
I thought of another way to handle this — perhaps closer to what you were looking for . . .
It's just an expansion of the basic pause routine that I use occasionally.
This is my "regular" Pause routine (using the Timer function):
Sub Pause(seconds As Single)
Dim startTime As Single
startTime = Timer 'get current timer count
Do
DoEvents 'let Windows "catch up"
Loop Until Timer > startTime + seconds 'repeat until time's up
End Sub
...so, it gave me an idea.
Honestly, I was a little surprised to discover that this works, since it's basically running two sections of code simultaneously.
Code for WaitForUserActivity :
Here's the code I used in the demo above:
Option Explicit
Public isPaused As Boolean
Sub WaitForUserActivity() 'THE 'RUN DEMO' BUTTON runs this sub.
Dim origSheet As String
isPaused = True 'flag "pause mode" as "on"
origSheet = ActiveSheet.Name 'remember current worksheet name
MsgBox "This will 'pause' code execution until you" & vbLf & _
"click the 'Continue' button, or select a different a worksheet."
Application.StatusBar = "PAUSED: Click ""Continue"", or select a worksheet."
Do 'wait for button click or ws change
DoEvents 'yield execution so that the OS can process other events
Loop Until (Not isPaused) Or (ActiveSheet.Name <> origSheet)
If isPaused Then 'the active worksheet was changed
MsgBox "Worksheet '" & ActiveSheet.Name & "' was selected." _
& vbLf & vbLf & "Now the program can continue..."
Else 'the button was clicked
MsgBox "The 'Continue' button was clicked." _
& vbLf & vbLf & "Now the program can continue..."
End If
Application.StatusBar = "Ready"
End Sub
Sub btnContinue() 'THE 'CONTINUE' BUTTON runs this sub.
isPaused = False 'flag "pause mode" as "off"
End Sub
To run the demo:
place the above code in a regular module
make sure the workbook has at least two worksheets
create two command buttons:
one for the "Run Demo" button, assign macro: WaitForUserActivity
one for the "Continue" button, assign macro: btnContinue
click the "Run Demo" button
The key command in the code is the DoEvents Function, which "yields execution so that the operating system can process other events."
DoEvents passes control to the operating system. Control is returned after the operating system has finished processing the events in its queue and all keys in the SendKeys queue have been sent.
DoEvents is most useful for simple things like allowing a user to cancel a process after it has started, for example a search for a file. For long-running processes, yielding the processor is better accomplished by using a Timer or delegating the task to an ActiveX EXE component - and the operating system takes care of multitasking and time slicing.
Any time you temporarily yield the processor within an event procedure, make sure the procedure is not executed again from a different part of your code before the first call returns; this could cause unpredictable results.
Further details (and warnings) at the source.
Original Answer:
Some suggested solutions:
Instead of "stopping" the code you could prompt the user to specify which worksheet.
The easiest way would be with an InputBox where the user would enter an ID number or otherwise identify the worksheet.
More complicated but more robust and professional-looking would be a custom dialog box with the help of a userform. There are several examples and tutorials online such as this one.
You could "pause" execution to give the user a set amount of time to select a worksheet, with a simple timer loop, ad you could even check the worksheet name to see if the user picked a new one, something like this:
Dim startTime As Single, shtName As String
If ThisWorkbook.Worksheets.Count = 1 Then
MsgBox "There is only one worksheet in this workbook."
Else
shtName = ActiveSheet.Name 'get name of active sheet
MsgBox "You have 5 seconds to select a worksheet after clicking OK.", _
vbOKOnly + vbInformation, "Select a worksheet... fast!"
startTime = Timer
Do
DoEvents
Loop Until Timer > startTime + 5
'check if user picked a new worksheet
If ActiveSheet.Name = shtName Then
MsgBox "You didn't select a new worksheet!"
Else
MsgBox "Thanks for selecting a new worksheet!"
End If
End If
It's a little hoakey but could work, especially if proper checks to make sure you've got the correct worksheet now.
I suppose you could create an worksheet event procedure that would run when a worksheet is activated, and checked a global variable to see if your "import procedure" was running, and if so, resume your code... but that would be messy and confusing and would require the code to exist in the workbook you're "importing".
Or, better than any of those would be to programmatically/logically determine which worksheet you need based on the contents of the worksheet. Is there a title? A certain date? Maybe the newest worksheet? Something in a certain cell? There must be something that differentiates it from the others.
Hopefully this gives you some ideas towards a non-linear solution. 😉
As in whole, I would recommend ashleedawg's solution, but if you
insisted on maintaining your code structure, your code could look
something like this:
You can distinguish between amount of Sheets a Workbook has using .Count property of the Sheets object (or Worksheets if you do not want to include Charts) and use InputBox to check for the sheet you want to look for.
MyFile = Application.GetOpenFilename()
Workbooks.Open (MyFile)
If ThisWorkbook.Sheets.Count = 1 Then
ThisWorkbook.ActiveSheet.Copy After:=wbook.Sheets(1)
ThisWorkbook.ActiveSheet.Name = "Selected File"
Else
Dim checkfor As String
checkfor = InputBox("What Sheet should I execute the code for?")
Dim i As Integer
For i = 0 To ThisWorkbook.Sheets.Count
If Trim(LCase(checkfor)) = Trim(LCase(Sheets(i).Name))) Then
ThisWorkbook.Sheets(i).Copy After := wbook.Sheets(1)
ThisWorkbook.Sheets(i).Name = "Selected file"
End If
Next i
End If
Workbooks.Open (MyFile)
ActiveWorkbook.Close SaveChanges:=False
Might need some further tweaking, because I was unsure what exactly you wanted to achieve.
I have a form that queries records that the user may want to edit. I want the user to only be able to save the record if they've clicked the 'Save' button. Hitting the 'Close' button will prompt the user if they haven't saved yet, and may ask if they want to save.
I'm encountering a problem when the user changes through the records: I want to have a Y/N message box prompt the user for saving the changes they made to the previous record, otherwise their changes will be discarded. I have the following code set up:
Private Sub CmdCloseForm_Click()
If Me.Dirty Then
'checks that needed fields are completed
If IsFormValidated = False Then
If MsgBox("Required fields aren't filled." & vbCrLf & "Would you like to close this form without saving?", vbYesNo + vbQuestion + vbDefaultButton2, "Warning") = vbNo Then
Exit Sub
End If
Else
'checks if form has been saved already
If mSaved = False Then
Select Case MsgBox("Form hasn't been saved. Do you want to save and close?" & vbCrLf & "If you click 'No' the form will close without saving.", vbQuestion + vbYesNoCancel, "Save As")
'selecting yes will save and close form
Case vbYes:
mSaved = True
'selecting no will close the form w/o saving
Case vbNo:
mSaved = False
'selecting cancel will cancel out of the prompt
Case vbCancel:
Exit Sub
End Select
ElseIf mSaved = True Then
'if form has been previously saved, will finally close the form
If MsgBox("Would you like to close this form?", vbYesNo + vbQuestion + vbDefaultButton2, "Close Form") = vbNo Then
Exit Sub
End If
End If
End If
End If
DoCmd.Close acForm, Me.Name, acSaveNo
End Sub
'won't save automatically unless mSaved is true
Private Sub Form_BeforeUpdate(Cancel As Integer)
'if mSaved = False then the record won't save
If mSaved = False Then
Cancel = True
Me.Undo
Cancel = False
End If
End Sub
I pretty much want the 'CmdCloseForm' Message Boxes to run when the user moves on to the next record. Is there any way to do this?
If you really want to ask user about saving changes for each row, ask in Form_BeforeUpdate and in Form_BeforeDelConfirm. Those events will be fired each time when user changes edited record, or when subform with edited records loses focus, or user closes form with data. But this is not good solution because messageboxes will be too annoying. The better way is to copy edited data to temporary table, allow user edit the data and copy back to source table changed data when user clicks "Save". It's quite simple if you don't need multi-user data editing, in this case you will need some additional code for avoiding collisions.
I wanted to create a small macro that forces the user to use the SaveAs dialog in MS Excel 2010 ("MS Office Professional Plus 2010" in case it makes a difference) instead of just saving the file under the same name. I saved this procedure under the workbook object:
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
If Not SaveAsUI Then
msg = "Use the 'Save As' dialog to save a new version of the file"
Style = vbOKCancel
Query = MsgBox(msg, Style)
If Query = vbOK Then
Application.Dialogs(xlDialogSaveAs).Show
ElseIf Query = vbCancel Then
Cancel = True
End If
End If
End Sub
It all works well: I press "Ctrl-S" and get the prompt. I click "OK" and use the SaveAs dialog to save the file under a different name. But as soon as I hit the "Save" button in the dialog, Excel crashes.
I am probably using the xlDialogSaveAs command in a wrong way but I just cannot figure out why this does not work. There are no error messages in the debugger. There is no other VBA code anywhere else in the workbook. I am trying to save the workbook as an .xlsm file (the SaveAs dialog correctly defaults to it).
Is there anyone out there who can help me?
Try,
If Query = vbOk Then
Application.Dialogs(xlDialogSaveAs).Show
End If
Cancel = True.
I suspect there is a problem trying to save in the original call and trying to save in the new dialog you open up
I have been surfing the wed looking for an answer to what I thought it was a pretty simple question, for a newbie like me.. But havent found any so here I am!. To keep things simple, i'll attach the two bits of my code that are giving me a bit of a headache. What I intend to do is to switch from a Read-Only mode to a Write Allowed state by clicking a button. In order to do so, I want the routine to check for changes on the record and, if there are any ask the user to decide whether to save the changes or discard them.. While testing this particular button, I found out something quite "funny".. It seems to work well when no changes are introduced. Switches perfectly from read-only to write-allow states. However, when in write-allow and changes have been made, if the "Save option" has been selected/clicked then it does not lock the content although all the other subroutines and changes are implemented.
My other problem, closely related to this, is that I cant find a way to set a "saving point" for the undo option. I would like to find a way to "save a record" so when the undo button is pressed doesnt undo all the changes that the record has suffered since the database has been firstly opened (as its currently happening), but since the button has been pressed. I tried the DoCmd Save function but is not behaving as I was expecting (Note: the last 'else' has been my last try to this problem but, again, its not working as expected)
Many thanks for all your future collaboration,
Alex
Private Sub Command28_Click()
If Form_AODNewRecord.AllowAdditions = True Then
Call isModified_Save
Form_AODNewRecord.AllowAdditions = False
Form_AODNewRecord.AllowEdits = False
Form_AODNewRecord.AllowDeletions = False
MsgBox "Read Only. Addition, Edits and Deletions are now locked"
Command28.Caption = "LOCKED"
Else
Form_AODNewRecord.AllowAdditions = True
Form_AODNewRecord.AllowEdits = True
Form_AODNewRecord.AllowDeletions = True
MsgBox "New records, edits and deletions are now allowed"
Command28.Caption = "Lock mode OFF"
End If
MsgBox Form_AODNewRecord.AllowAdditions
End Sub
Sub isModified_Save()
If Form_AODNewRecord.Dirty Then
Dim strMsg As String, strTitle As String
strMsg = "You have edited this record. Do you want to save the changes?"
strTitle = "Save Record?"
If MsgBox(strMsg, vbQuestion + vbYesNo, strTitle) = vbNo Then
Me.Undo
Else
DoCmd.OpenTable "AOD Type", acViewPreview, acReadOnly
DoCmd.Save acTable, "AOD Type"
DoCmd.Close acTable, "AOD Type", acSaveYes
End If
End If
End Sub
I think you want the following code. You cannot open a table that the form is bound to and affect the form, when you open the table, you have two different instances and they do not affect one another.
An odd side effect of running a macro in Office applications is that it clears the Undo list, so I created a small macro that just shows a message "Undo cleared" and that is exactly what it does!
As an aside, rename your control with meaningful names, such as cmdLock, rather than Command28, you will thank yourself later.
Private Sub Command28_Click()
If Me.AllowAdditions = True Then
Call isModified_Save
Me.AllowAdditions = False
Me.AllowEdits = False
Me.AllowDeletions = False
MsgBox "Read Only. Addition, Edits and Deletions are now locked"
Command28.Caption = "LOCKED"
Else
Me.AllowAdditions = True
Me.AllowEdits = True
Me.AllowDeletions = True
MsgBox "New records, edits and deletions are now allowed"
Command28.Caption = "Lock mode OFF"
End If
''MsgBox Me.AllowAdditions
End Sub
Sub isModified_Save()
If Me.Dirty Then
Dim strMsg As String, strTitle As String
strMsg = "You have edited this record. Do you want to save the changes?"
strTitle = "Save Record?"
If MsgBox(strMsg, vbQuestion + vbYesNo, strTitle) = vbNo Then
Me.Undo
Else
''Save record
Me.Dirty = False
''Clear undo list
DoCmd.RunMacro "ClearUndo"
End If
End If
End Sub