Document_Open() does not work in other files - vba

I am writing a VBA project containing 1 module (m1) and 1 userform (uf).
In "ThisDocument" I am calling a public sub from "m1" that initializes a collection I then refer to in the userform. This works perfectly fine until I deploy this project to other files.
I save the file as a .dotm in the %Appdata%/Microsoft/word/startup folder so I can use the project in all of my word files. But as soon as I try to use my project in other files the userform opens itself as designed but the collection is empty.
What could be the problem here?
Manually calling the initialization method from the userform works fine.
'----------------------------------------------ThisDocument
Private Sub Document_Open()
initBetaCollection
End Sub
'----------------------------------------------m1
Option Explicit
Public beta As Collection
Sub initBetaCollection()
Set beta = New Collection
beta.Add Array("0041", "A"), Key:="0041"
'...
End Sub
'----------------------------------------------uf
Option Explicit
Private Sub txtSearch_Change()
Dim arr As Variant
Dim search As String
'Defining the textinput as "search"
search = txtSearch.Value
For Each arr In beta
If search <> "" Then 'No empty search field
If arr(1) Like "*" & search & "*" Then 'Match found
lbResults.AddItem arr(0)
End If
End If
Next
End Sub
I get the: Run Time Error '424' object required

The problem with using Document_Open in the ThisDocument class or a macro named AutoOpen in a regular module is that both execute specifically for this document (or documents created from the template), only.
In order to have an application-level event, that fires for all documents opened, it's necessary to work with application-level events.
For this, first a class module is required. In the class module, the following code is needed:
'Class module named: Class1
Public WithEvents app As Word.Application
Private Sub app_DocumentOpen(ByVal Doc As Document)
'MsgBox Doc.FullName & ": on Open"
'Code here that should fire when a document is opened
'If something needs to be done with this document, use
'the Doc parameter passed to the event, don't try to use ActiveDocument
End Sub
Then, in a regular module, an AutoExec macro can be used to initialize the class with event handling. (AutoExec: fires when Word starts and loads a template with a macro of this name.)
Option Explicit
Dim theApp As New Class1
Sub AutoExec()
'MsgBox "AutoExec"
Set theApp.app = Word.Application
End Sub
'Sub AutoOpen()
' MsgBox "Open in AutoOpen" - fires only for this document
'End Sub

Related

Is there a VBA event triggered when a Word document is closed?

I'm trying to use Normal.dotm as a macro storage object analogous to the Personal.xlsb object within Excel.
The built-in Document_Close() event seems like it cannot be triggered unless it's included within a specific document's ThisDocument object.
I've also tried to use this Application_Quit() event like so but to no avail:
Private Sub Application_Quit()
Msgbox "closing word"
End Sub
Is it possible to listen for closing of the word application like it is in excel with Auto_Close(), etc?
Attempt for #BigBen based on this documentation
Class Module "Event Class Module"
Public WithEvents App As Word.Application
Normal Module "Module 1"
Dim X As New Class1
Sub Register_Event_Handler()
Set X.App = Word.Application
End Sub
Private Sub App_Quit()
MsgBox "closing word"
End Sub
There are two basic ways to capture when any Word document closes:
Use a macro named AutoClose in any module of Normal.dotm (or any template loaded as an add-in). This is the "old-fashioned" way that comes from the WordBasic (Word 2.0 and 6.0) days.
Note that if any other document (or template) has AutoClose that this will over-ride the macro running a "more general" level. In other words, the document- (or template-) specific code takes priority.
Sub AutoClose()
MsgBox "The document " & ActiveDocument.FullName & " is closing."
End Sub
Work with an application-level event. This requires both a class module and a "plain" module. The event code is in the class module; the code to initialize the class must be in a "plain" module.
This event's name is DocumentBeforeClose and can be cancelled, both by the user and by code (see the Cancel parameter in the event signature).
In contrast to AutoClose, if more than one document or template has the event running all will fire - there is no priority or "override".
Class module named clsEvents
Public WithEvents app As Word.Application
Private Sub app_DocumentBeforeClose(ByVal Doc As Document, Cancel As Boolean)
MsgBox "The document " & Doc.FullName & " is being closed."
End Sub
Module
Public myEvents As New clsEvents
Public Sub StartEvents()
Set myEvents.app = Word.Application
End Sub
Public Sub EndEvents()
Set myEvents.app = Nothing
End Sub
Note that if both types of event are present the AutoClose fires after DocumentBeforeClose.
Note also that there is a document-specific event that will fire only for that document: Document_Close. This event must be in the ThisDocument class module of that document.
Private Sub Document_Close()
End Sub
Here are some codes I used to raise an event when the protected Word document is being closed. The purpose is to send a message box BEFORE the preview document is CLOSED. User is able to abort the closing event, modify the document and close the document again. The simpler Word document Document_Close() event handler does not support CANCEL = TRUE.
Step 1. Add following code into a CLASS (codes must be in class module). I named the class as 'EventClassModule', I made a public declaration 'App', these names are referenced in the module.
Public WithEvents App As Word.Application
Private Sub App_DocumentBeforeClose(ByVal Doc As Document, Cancel As Boolean)
If vbYes = MsgBox("Do you need to modify the certificate?", vbYesNo) Then
If ActiveDocument.ProtectionType <> wdNoProtection Then
ActiveDocument.Unprotect
End If
Cancel = True
End If
End Sub
Step 2: Add following code into a module. The class name 'EventClassModule' and the public declaration 'App' are referenced here. The code 'Set X.App = Word.Application' will point to the Word application object, and the event procedures in the class module will run when the events occur.
Dim X As New EventClassModule
Sub abc()
Set X.App = Word.Application
Dim Doc As Word.Document
Set Doc = ActiveDocument
If Doc.ProtectionType <> wdNoProtection Then
Doc.Unprotect
End If
ActiveDocument.Range.Text = Time
Doc.Protect wdAllowOnlyReading
End Sub

Word VBA force save as .docm

I've ended up cleaning up someone's mess on a Word document that's used throughout my company. It is a macro-heavy document that needs to be saved as a .docm exclusively. I'm worried about it getting "save as"-d as something other than a .docm, but I can't seem to find a way to limit the save as file picker or swap out the extension on save as while still using VBA.
How can I achieve this?
Edit: Some of the things I've tried, to no avail:
This has the right idea, but doesn't actually limit the filetypes down on save https://answers.microsoft.com/en-us/msoffice/forum/msoffice_word-mso_other/how-to-set-path-for-wddialogfilesaveas-dialog/535b7f9c-9972-425c-8483-35387a97d61d
Towards the bottom, Microsoft says that SaveAs isn't compatible with filter.clear and filter.add https://msdn.microsoft.com/en-us/library/office/aa219834(v=office.11).aspx
In order to hijack the native "SaveAs" dialog in Word, you need to lever the Application-level event for DocumentBeforeSave, and then call the FileDialog manually, in order to validate the extension.
1. Create a standard code module and name it modEventHandler. Put the following code in it.
Option Explicit
Public TrapFlag As Boolean
Public cWordObject As New cEventClass
'You may not need these in a DOCM, but I needed to implement this in an ADD-IN
Sub TrapEvents()
If TrapFlag Then
Exit Sub
End If
Set cWordObject.DOCEvent = Application
TrapFlag = True
End Sub
Sub ReleaseTrap()
If TrapFlag Then
Set cWordObject.DOCEvent = Nothing
Set cWordObject = Nothing
TrapFlag = False
End If
End Sub
2. Create a Class Module called cEventClass and put this code in the module:
Option Explicit
Public WithEvents DOCEvent As Application
Private Sub DOCEvent_DocumentBeforeSave(ByVal Doc As Document, SaveAsUI As Boolean, Cancel As Boolean)
' Do not prevent SAVEAS for *other* documents
If ObjPtr(Doc) <> ObjPtr(ThisDocument) Then
Exit Sub
End If
If SaveAsUI Then
' The user has invoked SAVE AS command , so we will hijack this and use our own FileDialog
Call CustomSaveAs(Doc)
' Prevent duplicate appearance of the SAVEAS FileDialog
Cancel = True
End If
End Sub
Private Sub CustomSaveAs(ByRef Doc As Document)
Dim fd As FileDialog
Dim filename$
Set fd = Application.FileDialog(msoFileDialogSaveAs)
fd.Show
If fd.SelectedItems.Count = 0 Then Exit Sub
filename = fd.SelectedItems(1)
If Not Right(filename, 4) = "docm" Then
' ### DO NOT EXECUTE this dialog unless it matches our .DOCM file extension
MsgBox "This document should only be saved as a DOCM / Macro-Enabled Document", vbCritical, "NOT SAVED!"
Else
fd.Execute
End If
End Sub
3. In ThisDocument module, do the following code:
Private Sub Document_Close()
Call modEventHandler.ReleaseTrap
End Sub
Private Sub Document_Open()
Call modEventHandler.TrapEvents
End Sub
How Does This Work?
ThisDocument raises the Document_Open event which calls on the TrapEvents procedure.
TrapEvents procedure creates a WithEvents instance of Word.Application class, exposing additional events to automation. One of these is DocumentBeforeSave.
We use the DocumentBeforeSave event to trap the SaveAs operation. If the User has requested a SaveAs, then we force the dialog with logic as created in CustomSaveAs procedure. This uses simple logic to test the file extension provided, and prevents the document from being saved if it is not a DOCM extension. If the FileDialog does receive a valid DOCM extension, then we Execute the dialog which saves the file as the new name.

Detect the event ActiveWorkbook.Save from personal.xlsm in excel

I have all macros stored in personal.xlsm and untill now I have used a standard filter to hide/show columns. The new feature I want to implement now is that each user can have their own filter if they would like to. So basically i look in a folder for a personal filter if it exist and if it does it use that filter instead of the standard one.
But my problem is that i want to load a personal filter on workbookOpen event and reset to standard filter on the beforeClose event. My question is if I can do this from personal.xlsm in a way? Or do I have to manually go through all 250 workbooks and add in thisworkbook module onOpen and beforeClose events to call my method createFilter and resetFilter?
Here is a link to personal.xlsm for those who are not familiar with that methodology
In personal.xlsm, create a class module named "Helper". Place the following code in it:
Option Explicit
Public WithEvents xlApp As Application
Private Sub Class_Initialize()
Set xlApp = Application
End Sub
Private Sub xlApp_WorkbookOpen(ByVal Wb As Workbook)
'Your code here
End Sub
Your code for loading a filter should go in the 'Your code here bit.
Add a standard code module and add this code:
Public MyHelper As Helper
Finally, in the Workbook_Open event of personal.xlsm, place this code:
Private Sub Workbook_Open()
Set MyHelper = New Helper
End Sub
Save personal.xlsm and restart Excel.
Here is the code in helper if someones needs it:
Private Sub xlApp_WorkbookOpen(ByVal Wb As Workbook)
Dim myPath As String
On error resume next
If InStr(UCase(ActiveWorkbook.Name), "PARTSLIST") And (InStr(UCase(ActiveWorkbook.Name), "FILTERPARTSLIST") = 0) Then
myPath = Left(ThisWorkbook.Path, InStrRev(ThisWorkbook.Path, "\") - 1)
If Dir(myPath & "\filterPartsList.xlsx") <> "" Then '
Call ColumnCreater.updateFilter
Else
Call ColumnCreater.filterCreationStandard
End If
End If
End Sub

Using Events with the Application Object in MS Word

Ms Word does not want to load my add-in. I want to call a userform on print event. Here is my code:
in module 1
Option Explicit
Private Sub App_DocumentBeforePrint(ByVal Doc As Document, Cancel As Boolean)
'Debug.Print Now & " " & "App_DocumentBeforePrint: " & Wb.FullName
Userform1.Show
End Sub
Sub InitializeApp()
Dim X As New EventClassModule
Set X.App = Word.Application
End Sub
in Document module
Private Sub Document_Open()
Call InitializeApp
End Sub
in EventClassModule
Public WithEvents App As Word.Application
in Userform1 Mode
Option Explicit
Private Sub UserForm1_Initialize()
End Sub
I used this 2 links to help me write this code
1) https://msdn.microsoft.com/en-us/library/bb221264%28v=office.12%29.aspx
2) https://msdn.microsoft.com/en-us/library/gg597509%28v=office.14%29.aspx
Can anyone tell why my code does not work?
As explained in the first link you show, the procedure App_DocumentBeforePrint needs to be in the CLASS module (EventClassModule, in your explanation), not in Module 1.
Other than that, it's not clear what you mean by "my add-in". Usually, I'd think of a template (or COM add-in) when this term is used that's being loaded as an add-in. I'm concerned whether Document_Open is actually being triggered to initialize your events. This event, in the ThisDocument module (in reality, it's a class) will only fire when the document containing this code is opened...
For anybody else who comes across this thread like I did, this is what worked for me:
in module 1
(your module shouldn't contain the event-based sub; also, X needs to be declared as a global variable rather than within the 'InitializeApp' sub)
Option Explicit
Dim X As New EventClassModule
Sub InitializeApp()
Set X.App = Word.Application
End Sub
in Document module
Private Sub Document_Open()
Call InitializeApp
End Sub
in EventClassModule
(your Class Module should contain the event-based sub)
Public WithEvents App As Word.Application
Private Sub App_DocumentBeforePrint(ByVal Doc As Document, Cancel As Boolean)
'''Your procedure here
End Sub

Document_New event on launching Word

I have a VBA addin which I want to run every time someone opens a document. It's working fine for opening existing documents (AutoOpen) and creating new documents from the File > New menu (AutoNew) but when I just open Word up for the first time, neither of these events are firing. The only event I can seem to hook into is the AutoExec event and this is not great as the document doesn't exist yet so ActiveWindow is null.
Can anyone help?
Sub AutoNew
MsgBox "New"
End Sub
Sub AutoOpen
MsgBox "Open"
End Sub
Sub AutoExec
MsgBox "Exec"
End Sub
I would start with DocumentOpen and NewDocument. An additional level of complexity exists if you need to support ProtectedView documents; Word triggers a different event. I've found that if I try to check for that event (and it doesn't occur) it raises an error. I didn't had much luck and eventually it wasn't worth the time I was spending. I've posted an example below of some code that opens the Style Pane when a document is opened or a new one created (assuming the add-in is loading) and expands the style margin in draft view if not already expanded.
In my UI module:
Dim X As New clsAppEvent 'This is in the declarations
Public Sub OnRibbonLoad(objRibbon As IRibbonUI)
Do While Documents.Count = 0
DoEvents
Loop ' I find this useful as sometimes it seems my ribbon loads before the document.
Call Register_Event_Handler
' Other stuff
End Sub
Private Sub Register_Event_Handler()
Set X.App = Word.Application
End Sub
Then, in a class module I call clsAppEvent:
Option Explicit
Public WithEvents App As Word.Application
Private Sub App_DocumentOpen(ByVal Doc As Document)
App.TaskPanes(wdTaskPaneFormatting).visible = True
End Sub
Private Sub App_NewDocument(ByVal Doc As Document)
App.TaskPanes(wdTaskPaneFormatting).visible = True
End Sub
Private Sub App_WindowActivate(ByVal Doc As Document, ByVal Wn As Window)
If Wn.StyleAreaWidth <= 0 Then
Wn.StyleAreaWidth = 60
End If
End Sub
Other than the caveats I've noted above, the one problem I've had is if a user has Auto code in their Normal template as well. This has only come up once so I haven't investigated it.
I wish I could find the site where I learned about this (and from which the Register_Event_Handler was derived. If I find it I'll add a comment.