Difference between print and print preview events in excel vba - vba

I have some code which intercepts the Before_Print event in excel to make sure that the user has filled in all the required fields before they print the sheet. However, I only want this code to fire when the user is actually printing, not when they are just calling the print preview.
Is there any way to tell in the Before_Print code whether the user is actually printing or just previewing?
The code that I currently have is (event stub was generated by excel):
Private Sub Workbook_BeforePrint(Cancel As Boolean)
If Not Sheet2.CheckAllFieldsFilled Then
Cancel = True
End If
End Sub

I don't think there is a neat way to determine if the event is a print preview or print request.
The solution below is not particularly neat and inconveniences the user slightly, but it works.
The code cancels the event and then prompts the user, based on their response it displays the print preview or prints.
Private Sub Workbook_BeforePrint(Cancel As Boolean)
Dim Print_or_Preview As XlYesNoGuess
Application.EnableEvents = False
Cancel = True
Print_or_Preview = MsgBox("Show Print Preview?", vbYesNo)
If Print_or_Preview = True Then
ActiveWindow.ActiveSheet.PrintPreview
Else
ActiveWindow.ActiveSheet.PrintOut
End If
Application.EnableEvents = True
End Sub

I think I would provide a very visible button for the user to push when wanting to Print Preview.
Make the button hide for printing (in the options for the button), and have the code simply say:
ActiveWindow.ActiveSheet.PrintPreview

To print you could do something like this:
ActiveWindow.SelectedSheets.PrintOut Copies:=1, Collate:=True
To Print Preview as was suggested:
ActiveWindow.ActiveSheet.PrintPreview
Each one would would require a different button, but either way I would strongly suggest testing if you really need both, because the preview button may work for your print option, especially since you would in most cases be able to print straight from the preview.
I may be wrong here, but I don't think so.
Just a heads up, the print option I posted here will directly print, it won't request options, because they have been coded into the script, if you want to change how many copies you wish to print, change the copies:= to whatever number you wish...
Enjoy :)

Related

How to ask for confirmation before allowing a Word document to close?

I work with 2 different keyboard layouts (qwerty and azerty), and am constantly switching between the two using Alt-Shift. Sometimes when I reach for Ctrl-Z to undo, I get instead Ctrl-W which closes the document. Generally speaking I would in these situations get a Save Prompt, which would alert me to my mistake. However, I am now using OneDrive, with Autosave on the documents I'm working on. As a result, there is never this save prompt. The document is instantly closed and, worse, the undo that I was trying to effect is no longer present when the document is re-opened.
I would like to use VBA attached to the Normal Template, such that, on closing any document created with the VBA template, I will check to see if Autosave is turned on, and if it is, confirm before closing with a messagebox.
Obviously if the user doesn't confirm (e.g. chooses Cancel) then the document should not be closed.
Initially I put my code on Document_Close, but soon realised that here it's too late to Cancel the close. This is no good to me.
Then, based on some online code, I put my code in a class module (EventClassModule). In the ThisDocument module, I initiate the class, and activate the event handler.
My code doesn't ever seem to run, or in anycase, breakpoints don't kick in.
I'm a little unsure of where Normal stops and my normal document begins...
In ThisDocument of Normal Template, I have this VBA
Dim X As New EventClassModule
Sub Register_Event_Handler()
Set X.App = Word.Application
End Sub
Private Sub Document_Open()
Register_Event_Handler
End Sub
And I have a class module called EventClassModule with the following
Public WithEvents App As Word.Application
Private Sub App_DocumentBeforeClose(ByVal Doc As Document, Cancel As Boolean)
Dim res As VbMsgBoxResult
If Me.AutoSaveOn Then
res = MsgBox("OK to Confirm", vbOKCancel, "Closing Document")
If res = vbCancel Then
Cancel = True
End If
End If
End Sub
I would like a prompt to confirm saving, any time a document is closed and the autosave option is turned off.
The Document_Open event handler will only fire if the Normal template is opened via File | Open. To get code to respond when starting Word you need to use an AutoExec routine. Simply rename your routine as below. You can find further info on Auto macros here
Public Sub AutoExec()
Register_Event_Handler
End Sub
Here is an easy way, include a method like this:
Public Sub DocClose()
' Disables Ctrl + W (but not other close methods, e.g. Alt + F4, menus, [X])
' Your code here:
ActiveDocument.Close
End Sub
Basically there are some method names that are used internally by MS-Word, and if you include them in your code they will prevent the default action. If you leave the sub empty it will effectively disable Ctrl + W.
See here for more information: Reserved method names in MS Word VBA
Advantage 1 of this technique
If you use Public WithEvents App As Word.Application and your project gets reset for any reason, and fails to restart... App will be Nothing and none of the events will fire.
Advantage 2 of this technique
On the occasions that you actually want to close a document... with this technique you wont get bugged by a (soon to become) annoying confirmation message - just sayin ;-)

Execute specific line vba

I couldn't find this asked anywhere. In Visual Basic (excel), I can hit F8 and cycle through each line. But lets say I want to begin the sub procedure, and then after executing the first 2 lines, I'd like to skip to line 200. Until now, I've always just dragged the yellow arrow to the desired line. This is really time consuming and I was wondering if there's any command to simply say "run current line where selected" or something.
Additionally, even if I could begin to run through line by line, and quickly move the yellow selected arrow to the desired line, that would also work.
If you have a 200-liner procedure that does so many things you'd like to skip most of it, it looks like you need to refactor a bit.
Extract "things the procedure is doing" into their own Sub procedures and Function scopes. If you have banner-like comments that say things like '*** do something *** then that's a chunk to extract into its own procedure already.
Stepping through that procedure could then involve stepping over (Shift+F8) the smaller procedures that do one thing, or break and skip the call altogether.
Right click on the line you want to jump to. Hit the Set Next Statement option in the menu. That's equivalent to dragging the arrow to that line. (Ctrl-F9 is the hotkey for this action.)
If you want it to quickly execute every line up to a certain line, set a breakpoint then hit run instead of stepping through the code line by line. Do this by clicking on the gray bar to the left side where the yellow arrow appears. A dark red dot should appear and the line should be highlighted in dark red. This tells visual basic to stop when it hits that line.
You can also comment lines out by starting them with an apostrophy.
Finally, you can break code into subroutines and execute them independently of eachother.
Sub Subroutine1()
'This is a commented out line. It does nothing.
MsgBox "Do stuff here"
End Sub
Sub Subroutine2()
Subroutine1 'This will run all the code in subroutine 1
MsgBox "Do more stuff here"
End Sub
In the above example, if you run Subroutine1 you'll get one message box popping up. If you run Subroutine2 you'll get two message boxes.
Unfortunately it is not possible to do what you ask directly.
However, you may comment out the lines of code above the code you want to be executed for example:
Sub Workbook_Open()
'Application.DisplayFullScreen = True
'Application.DisplayFormulaBar = False
'ActiveWindow.DisplayWorkbookTabs = False
''ActiveWindow.DisplayHeadings = False
Application.EnableEvents = True
Password = "1234"
ActiveWorkbook.Protect
ThisWorkbook.Protect (Password = "1234")
End Sub
You may use GoTos, but however this is not considered good practice and may actively harm your code:
Sub Workbook_Open()
GoTo ExecuteCode
Application.DisplayFullScreen = True
Application.DisplayFormulaBar = False
ActiveWindow.DisplayWorkbookTabs = False
ActiveWindow.DisplayHeadings = False
Application.EnableEvents = True
ExecuteCode:
Password = "1234"
ActiveWorkbook.Protect
ThisWorkbook.Protect (Password = "1234")
End Sub
This is how I do it - basically if I know that my code up to line 200 is working properly but I'm pretty sure there's an error between 200-300 then before compiling - scroll down to line 200 and mark it (to the left of the code). Then compile it - click F5 and it will execute everything up to line 200 - then you can step through each line thereafter individually.
I normally comment out lines of code that I don't want to run with apostrophes. Alternatively, you can break up your code into smaller procedures so that you can easily pick and choose what you want to test/run.
I found adding a module for testing and copy pasting snippets of code in it as a best way for troubleshooting.

Programming VBA in an Outlook form

I created my own Outlook form to use it as standard surface to enter certain orders instead of the normal message form. The creation, editing and sending works perfectly fine and in the next step I want to insert some code via VBA.
My problem is that I can´t access the objects of my form in the VBA editor. E.g. I want to show a message box when a certain checkbox is checked. According code would be:
Sub example()
If CheckBox1.Value = True Then
MsgBox("Checkbox 1 is checked.")
End If
End Sub
When I run the code I get the error that the object could not be found. The same goes for every other object, like textboxes or labels etc.
I guess the solution is pretty simple, like putting Item. or sth. like that in front of each object. But so far I wasn't able to find the solution.
I´m using Outlook 2010.
I know this is a year too late but you'll want to do something like this example below. It's kinda a work around but you can get whatever value was selected.
Sub ComboBox1_Click()
Set objPage = Item.GetInspector.ModifiedFormPages("Message")
Set Control = objPage.Controls("ComboBox1")
MsgBox "The value in the " & Control.Name & _
"control has changed to " & Control.Value & "."
End Sub
You should be able to get the value, just get a handle on the object you want using the Inspector
The following is an excerpt from here
When you use a custom form, Outlook only supports the Click event for
controls. This is a natural choice for buttons but not optimal for
controls like the combo box. You write the code by inserting it into a
form’s VBScript editor. You need to have the Outlook form open in the
Form Designer and click the View Code button found in the Form group
of the Developer tab.
Sub CheckBox1_Click()
msgbox "Hello World"
End Sub
The code page is fairly minimal with no syntax highlighting. I just tried this now and it does work. Dont forget to Publish your form to pick up the new changes.
I know this is almost 6 years late but, in VB and VBA, simply start with the form name. (And if that doesn't work, just keep going up a parent object and you'll get there.) So, your code becomes:
Sub example()
If MYFORMNAME.CheckBox1.Value = True Then
MsgBox("Checkbox 1 is checked.")
End If
End Sub
Of course, after typing "MYFORMNAME." you'll know if it will work because typomatic will kick in when the system recognizes "MYFORMNAME" after you hit the period.

Preventing close buttons from saving records in MS Access

In a Microsoft Access form, whenever the current record changes, any changes in bound controls are silently saved to the database tables. This is fine, but I don't want it to happen when a user closes a form, because it is the direct opposite of what many people would expect.
The best example is when you try to close an excel file with unsaved changes, it asks whether the changes should be discarded. This is exactly what I'm trying to achieve in Access, but can't find any way to trap the close button's event in VBA.
The form's Unload event is the first event that is triggered when someone clicks the close button, but by then the changes are already written to the database.
Is this at all possible, or do I have to create my own close buttons? I'm comfortable with writing large amounts of code for trivial things like this but I hate having to clutter the GUI.
You have to work with Form_BeforeUpdate event. Below is an example; however it does create a typical warning message: "You can't save this record at this time. Microsoft Access may have encountered an error while trying to save a record. ..." - depending on your database settings. You can use simple workaround below to avoid displaying of that message.
Private Sub Form_BeforeUpdate(Cancel As Integer)
Cancel = True
'Or even better you can check certain fields here (If Then...)
End Sub
Private Sub Form_Error(DataErr As Integer, Response As Integer)
If DataErr = 2169 Then
Response = True
End If
End Sub
Sean gave an almost correct answer but it leaves gaps.
In general, the FORM's BeforeUpdate is the most important form event. It is your LAST line of defense and ALWAYS runs prior to a record being saved regardless of what prompted the save (form close, new record, your own save button, clicking into a subform, etc.) Although I occasionally use the control's BeforeUpdate event just so the user gets the error message sooner, the bulk of the validation code I write runs in the Form_BeforeUpdate event. This is the event you MUST use if you want to ensure that certain controls are not empty. No control level event will do this reliably for all situations. Primarily because if the control never gets focus, no control level event ever fires. Form_BeforeUpdate is also the event you would use if your validation involves multiple fields. If you are using any other control or event level event, you are wasting your time. There is always away around your "trap" and your table almost certainly contains invalid data.
Regarding the OP's question. If you want to force people to use your own save button and prompt them if they don't then you need a form level variable as Sean's suggestion implied. The only difference, is that you need to set it to False, in the form's Current event NOT the Open event. You want the flag to be reset for EVERY new record, not just when the form opens. Then you set it to True in your save button click event, just before you force the record to save with DoCmd.RunCommand acCmdSaveRecord.
Then finally, in the Form_BeforeUpdate event, you check the value of the variable.
If bClose = False Then
If MsgBox("Do you want to save the changes?", vbYesNo) = vbNo Then
Cancel = True
If MsgBox("Do you want to discard the Changes?", vbYesNo) = vbYes Then
Me.Undo
End If
Exit Sub
End If
End If
this is code I have that checks to see if the form is being closed or saved.
Private Sub Form_BeforeUpdate(Cancel As Integer)
If Not UsingSaveButton Then
If MsgBox("Abandon Data?", vbInformation + vbYesNo) = vbNo Then
Cancel = True
Else
DoCmd.RunCommand acCmdUndo
End If
End If
End Sub
I have a Boolean Flag that is set to False on loading, and then when my Save button is used, I set it to true to allow the update to run through.
If the flag is not set, then they are leaving the record (either through going to a different record, or closing the form) so I ask if they actually want to save the changes.
The Cancel = True aborts the exit of the form or the move to a different record if any changes have been made.
The DoCmd.RunCommand acCmdUndo undoes any changes so they are not saved.
Actually, you cannot trap this, Access is working directly upon the table, every change is already being saved when the field looses the focus by moving to another field, record or a button.
Actually imho this is a great advantage compared to Excel
If you really want a behaviour similar to Excel you'd need to work on a copy of the table and some code for updating.
In the Form in the 'On Unload' event add the below code.
DoCmd.RunCommand acCmdUndo
DoCmd.Quit
Records no longer being saved when users close a form in any way.
** you can use exit button with this code**
Private Sub Button_Click()
If Me.Dirty = True Then
If MsgBox(" Save Change ", vbYesNo) = vbYes Then
Me.Dirty = False
Else
Me.Undo
End If
End If
DoCmd.Close acForm, "FormName"
End Sub

Is there a DocumentAfterPrint event in MS Word?

I am working on a project where I need to return a word document back to a certain state after it is printed. I have found a DocumentBeforePrint event but I cannot find a DocumentAfterPrint event.
Is it poorly documented or is there some other workaround that exists?
Here is one workaround based on subroutine names. I do not believe there is a specific DocumentAfterPrint event like you desire. Here's the code:
Sub FilePrint()
'To intercept File > Print and CTRL-P'
MyPrintSub
End Sub
Sub FilePrintDefault()
'To intercept the Standard toolbar button'
MyPrintSub
End Sub
Sub MyPrintSub()
Dialogs(wdDialogFilePrint).Show
'Your code here, e.g:'
MsgBox "I am done printing."
End Sub
UPDATE: Please note the gotchas in Will Rickards' answer below.
Looking at the application events I don't see it.
I don't see it in the document events either.
Please note on the workaround provided above, that is using the FilePrint and FilePrintDefault methods, you should read this site. These methods replace the built-in function. So you actually need to add code in there or have it generated for you to get word to actually print.
Also background printing can cause your code to execute before it has finished printing. If you really must run something after it has printed you'll need to disable background printing.
I don't believe any of the suggested work-arounds will work in Word 2010. However, I have had success by using the application.onTime() method at the end of the documentBeforePrint event to cause another procedure to be executed a few seconds later.