Writing application event procedures
Or how to intercept events affecting any open document, such as the user changing focus from one document to another
Article contributed by Dave Rado, with acknowledgements to Bill Coan
You can respond to certain application-level events using Auto macros – see: Running a macro automatically when Word starts or quits. However, to respond to other application events you need to create an application object declared “WithEvents” – i.e. declared in such a way that it will respond to events.
How to set up code that will respond to application events
If you open a template, press Alt+F11, and in the Project window of the VBA environment, double-click on “Microsoft Word Objects”, you'll see a built-in Class Module called “ThisDocument”. If you open this, you can use the list boxes on the toolbar to help you create document event procedures.
So it would make sense for there to be another built-in class module called “ThisApplication”; but Microsoft, in their wisdom, felt this would be too user-friendly, so you have to create your own ... Help is very unhelpful on the topic – there isn't even a definition in Help of what a Class is!
In essence, though, a Class is simply a container object which allows you to create properties, which are objects in their own right, and then create properties and methods for those objects.
Note that the “big picture” view of what we're about to do is this: |
|
|
We want to respond to events. |
|
We need an application variable (declared with WithEvents) to receive the events. |
|
We need a class module to serve as a container for the application variable. |
So here's how to create an application event procedure:
1. |
Create an Addin; that is, a .dot file stored in Word's startup directory so that it is global. |
||||||||||||||||||||||||||||||
2. |
Open the Addin, and in the VB Editor, select Insert + Class Module. Rename the Class Module from Class1 to ThisApplication. Note: The Class Module and all the variables in this article can be called whatever you like; but I have tried to make all the names as meaningful as possible. I like “ThisApplication” because it shows that it is functionally analogous to the built-in “ThisDocument” class module. But note that your “ThisApplication” object is simply a container. It doesn't actually represent an application. It represents all the properties of the Class Object, whatever we define those properties to be. |
||||||||||||||||||||||||||||||
3. |
Insert the following code in the Class Module: Option Explicit This specifies that oApp is an object variable which will be used to respond to the events triggered by the ActiveX object Word.Application. By declaring it publicly, you have made oApp a property of the class object ThisApplication. At this point you are simply declaring what type of object oApp is (its type being a Word Application object), and declaring the fact it will respond to events. You are not actually assigning the oApp variable to the Word application yet – that comes later. So the oApp object doesn't actually exist yet (and no area of memory has been set aside for it yet). The WithEvents keyword can only be used in Class Modules and can only refer to ActiveX – i.e. OLE-compliant – objects. |
||||||||||||||||||||||||||||||
4. |
Now select Insert + Module. In the Module, insert the following code: Option Explicit Where “ThisApplication” is the name of your Class Module. The statement creates an object variable oAppClass which references (is a pointer to) the class object ThisApplication which you created earlier. Using the keyword “New” in the declaration creates a new instance of the class object – that is, it loads an instance of the class into memory and makes the variable oAppClass point to the newly created instance. From now on, you can refer to this new instance of the class by using the oAppClass variable. Now add the following code in the same Module:
Public Sub AutoExec() The oApp object is a property of the class object oAppClass, because you declared it as a public variable in the Class Module in step 3. The above code creates an instance of the oApp object (loads it into memory), and makes it actually refer to the Word.Application object. In effect it makes the oApp object exist. To put it another way, you have now assigned the actual Active-X Word Application to the variable, whereas previously you had only declared what type of variable it was going to be. By calling your procedure AutoExec, you make it run automatically when your Addin loads – and your Addin will automatically load when Word loads. |
||||||||||||||||||||||||||||||
5. |
Now run the AutoExec macro, by clicking in it and pressing F5, to initialise the oApp object. |
||||||||||||||||||||||||||||||
6. |
Switch to your ThisApplication Class Module. You'll see two list boxes on the toolbar. If you pull down the one on the left, and change it from “(General)” to “oApp”, a procedure called oApp_Quit() will be created. If you then pull down the list box on the right, you'll see several other application events to choose from (and you can delete the oApp_Quit() procedure later, if you don't want it):
Note that (rather confusingly) these are all Application events (even
though some of them refer to documents doing things) as distinct from Document events such as
Document_Close.
Because it is an Application event, for instance, a procedure called
oApp_DocumentBeforeClose will fire when any document closes;
whereas because it is a Document event, a Document_Close procedure will only
fire when documents based on the template that the Document_Close procedure is
stored in are closed. |
||||||||||||||||||||||||||||||
7. |
Having inserted the event procedure you want using the listboxes, add your
code, and you're done. |
Note: If you want to monitor application events affecting documents based on a specific template, you can either store the event code in a global Addin as described above and test for which template is in use by using ActiveDocument.AttachedTemplate; or you can store the event code in the “ThisDocument” Class Module of the relevant template.
For an example of the latter, see: How can I prevent users from editing the header of a document in Word 2000 or higher?
Summary of set-up procedure
oApp_Quit
An oApp_Quit event procedure works in exactly the same way as an AutoExit macro – it fires when Word quits. Unfortunately, both the AutoExit macro and the oApp_Quit procedure fire after any “Do you want to save changes” dialogs have appeared.
There is no event of the Application object analogous to an AutoExec macro,
however.
oApp_DocumentChange
The DocumentChange event triggers whenever you open/close/create a document and whenever you change focus from one document to another. If the event code is stored in an Addin, it will be global.
In Word 2000, there are separate events you can use for each of these – oApp_NewDocument for when a new document is created, etc. But in Word 97, you can use oApp_DocumentChange to simulate them.
Even in Word 2000, simulating them sometimes works better than using the “proper” events.
For more details, see: How to create global event procedures similar to AutoOpen, AutoNew and AutoClose, without using Normal.dot.
oApp_DocumentBeforeClose, oApp_DocumentOpen, oApp_NewDocument, oApp_ WindowActivate, oApp_WindowDeactivate
All of these combined – at least in theory – replace (and build on) the functionality of oApp_DocumentChange.
The oApp_DocumentBeforeClose, oApp_DocumentOpen, oApp_NewDocument event procedures are covered in the article: How to create global event procedures similar to AutoOpen, AutoNew and AutoClose, without using Normal.dot.
The oApp_ WindowActivate, oApp_WindowDeactivate events are self-explanatory. It's safest to use the list boxes on the toolbar to insert the event procedures, as the syntax is complicated.
oApp_DocumentBeforePrint, oApp_DocumentBeforeSave
These are discussed in the article: Intercepting events like Save and Print.
oApp_WindowBeforeDoubleClick, oApp_WindowBeforeRightClick, oApp_WindowSelectionChange
Again, these are self-explanatory, and it's safest to use the list boxes on the toolbar to insert the event procedures, to ensure that you get the syntax right.
Using the WindowBeforeDoubleClick and WindowBeforeRightClick events seem to have a significant performance hit on Word; but the WindowSelectionChange events seems to work well (subject, that is, to the code you put in it).