Word AfterSave event?

Add-in Express™ Support Service
That's what is more important than anything else

Word AfterSave event?
Is there an After Save event in Word? 
Christopher Cardinal

Posts: 133
Joined: 2005-05-17
I have an addin that managed custom properties on a word document. The properties relate back to an external system and are meant to be unique to the document. When the user invokes the SaveAs menu item, i need to empty the current custom properties (if set). However, the BeforeSave event is of course *before* - and there does not appear to be an AfterSave such that I could wipe out the properties if the document name has changed (i.e. it is a new file). Is there a way/workaround for this? I can imagine intercepting the Save and SaveAs calls - but that's a tremendous amount of work for such a small thing.
Posted 22 Dec, 2008 11:43:13 Top
Sergey Grischenko

Add-in Express team

Posts: 7228
Joined: 2004-07-05
Hi Christopher.

There is not the AfterSave event in the Word Object Model.
You can use the ADXBuiltInControl component to intercept Save and SaveAs commands.
Posted 22 Dec, 2008 13:15:31 Top
Andrew Painter

Posts: 42
Joined: 2008-06-26
There is no AfterSave Event but using Application.BeforeSave in conjunction with a While loop that checks the boolean quality of the ActiveDocument.Saved property...
Posted 22 Dec, 2008 15:49:27 Top
Sergey Grischenko

Add-in Express team

Posts: 7228
Joined: 2004-07-05
Hi Andrew.

Thank you for the suggestion. I guess the loop should be run in a timer.
Posted 23 Dec, 2008 07:11:05 Top
Andrew Painter

Posts: 42
Joined: 2008-06-26
The loop could be run as a background thread with a timer, but if you're working with BeforeSave in MS Word you're actually already dealing with the MS Word Thread, your Addin base Thread, and the event that passes from MS Word to the Addin.

Since your Addin probably shouldn't be trying to do additional processing while MS Word is saving anyway, there's no reason your BeforeSave Event can't just go into a "Until Document.Saved" conditional loop during BeforeSave and then either raise a custom event or just process additional code when the Until loop finishes... IE: AfterSave.
Posted 25 Dec, 2008 16:01:23 Top
Sergey Grischenko

Add-in Express team

Posts: 7228
Joined: 2004-07-05
Hi Andrew.

The user can cancel the document saving. What we should do in this case?
Anyway, thank you for the help. I hope your workaround will help Christopher.
Posted 26 Dec, 2008 08:47:35 Top
Andrew Painter

Posts: 42
Joined: 2008-06-26
I was mistaken in the assumption that the MS Word Application Thread and the DotNET component Thread were separate.

In addition, BeforeSave fires before the Save Dialog appears instead of before the actual save, so when sticking on a Do Until the MS Word Save dialog never actually appears. That means as you said at the start, Sergey, a timer or separate thread is needed.

This seems like more a failing in Microsoft's development of the OOM than anything else. I'm going to look at designing a good fix and I'll post some code in the immediate future.

The last line of my original post Italicized for emphasis. There is no good fix here, short of rewriting the Save and Save As commands.

I spent this afternoon working on this, only to realize that in order to use BeforeSave with the .Saved property without hanging MS Word or causing horrendous problems requires API calls direct to Windows, which defeats the purpose of working with DotNET in the first place.

Best solution: Hook into the Save and SaveAs commands in MS Word and prevent them from doing their normal thing.

Posted 26 Dec, 2008 20:24:56 Top
Sergey Grischenko

Add-in Express team

Posts: 7228
Joined: 2004-07-05
Hi Andrew.

I agree with you. Please let me know if you find another solution.
Posted 30 Dec, 2008 11:34:54 Top
Andrew Painter

Posts: 42
Joined: 2008-06-26
This has been really grating on my nerves since I gave it up the other day, and finally I decided I would not be happy until it was resolved.

So here's a custom Word_AfterSave Event. We're using the BeforeSave Event to catch incoming saves from MS Word and add the saving document to the queue IF it doesn't already exist.

I had originally broken this into several QUOTE panels so I could discuss each segment in a tutorial fashion, but at this point I'm just going to post it with full commentation and hope for the best. Any questions, feel free to ask!

    ''' This is the trappable Event we'll use. It allows 
    ''' us to do our checking in the background but handle 
    ''' the actual AfterSave Event in our Addin Thread. 

    Private Event WordDoc_AfterSave(ByVal Doc As Word.Document)

    ''' This WithEvents variable allows us to capture 
    ''' Events in DotNET from MS Word. This can't be done 
    ''' from the ADX WordApp Property! 
    ''' We're using this to trap the BeforeSave Event, 
    ''' since you can't have an AfterSave without a 
    ''' BeforeSave. 

    Private WithEvents MS_WordApp As Word.Application

    ''' Since checking is a process that we want to repeat 
    ''' as long as the Addin is running in Word, we need 
    ''' to treat it a little more specially than a standard 
    ''' background Thread. A Delegate does the trick. 

    Private Delegate Sub del_SaveCheck()

    ''' We need a usable object in memory to use our Delegate. 

    Private th_SaveCheck As del_SaveCheck

    ''' This is the special treatment I mentioned earlier - 
    ''' Every time the separate thread finishes, it needs 
    ''' to be started straight back up. The AsyncCallback 
    ''' allows us to do this. 

    Private th_SaveDone As AsyncCallback

    ''' We MAY have multiple documents open in MS Word at any 
    ''' given time, and because it AutoSaves there is always 
    ''' the possibility that one document will autosave at the 
    ''' exact same time as another, or at the same time as a 
    ''' user save occurs. 
    ''' We also have two threads here - the Addin Primary 
    ''' in the Foreground and our new Asynchronous Delegate 
    ''' in the background. We MUST NOT attempt to access 
    ''' this object in memory from more than 1 thread at a 
    ''' time! 
    ''' The Queue Class allows us to chain documents as they're 
    ''' saved, remove them as they're processed in the AfterSave 
    ''' Event, and also to instantiate a SyncLock for Thread 
    ''' Safety. 

    Private queue_Docs As New System.Collections.Queue

    ''' AddinModule.AddinStartupComplete is a good event 
    ''' to use for placing variable definitions since 
    ''' MS Word will be started, its default blank 
    ''' document will be created, and the Addin Module will 
    ''' be done initializing itself. 

    Private Sub AddinModule_AddinStartupComplete( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles MyBase.AddinStartupComplete

        'Me.WordApp is set as an ADX Property by default. 
        'We're just pushing that Property into our existing 
        'WithEvents variable so we can actually trap Events. 
        Me.MS_WordApp = Me.WordApp

        'AddHandler is how we redirect Events from WithEvents 
        'variables to subroutines. We're only concerned 
        ' with handling BEFORESAVE. 
        AddHandler Me.MS_WordApp.DocumentBeforeSave, _
        AddressOf MS_Wordapp_BeforeSave

        'Now we need to instantiate our Delegate and point it 
        'at our repeating codeblock to check for and fire 
        'the AfterSave Event. 
        th_SaveCheck = New del_SaveCheck( _
        AddressOf th_AfterSave)

        'We also need to instantiate our AsyncCallback and point it 
        'at the codeblock that fires every time our Delegate Thread 
        th_SaveDone = New System.AsyncCallback( _
        AddressOf th_AfterSaveDone)

        'Last, kick off the repeating background thread. 
        th_SaveCheck.BeginInvoke(th_SaveDone, th_SaveCheck)
    End Sub

    Private Sub MS_WordApp_BeforeSave( _
    ByVal Doc As Word.Document, _
    ByRef SaveUI As Boolean, _
    ByRef Cancel As Boolean)

        'SyncLock the Queue object to prevent thread collision. 
        SyncLock Me.queue_Docs
            'Check if the saving document is already queued. 
            If Not queue_Docs.Contains(Doc) Then
                'If it's not queued, queue it. 
            End If
        End SyncLock
    End Sub

    ''' The th_AfterSave sub is our delegate target. 
    ''' This codeblock runs repeatedly in a backgrounded thread 
    ''' while the Addin is active. 

    Private Sub th_AfterSave()
        'Check to see if there are any documents in the queue. If not, 
        'save the headache and just keep checking. 
        If Me.queue_Docs.Count > 0 Then
            'SyncLock the queue object to prevent thread collision. 
            SyncLock Me.queue_Docs
                'Queue.Peek allows us to grab the next object in queue 
                'without removing it from queue... 
                Dim curDoc As Word.Document = queue_Docs.Peek
                'Try gets us around two major problems: 
                '1) That while a Save Dialog is up in MS Word, MS Word throws 
                ''' fatal exceptions back through the COM interface. 
                '2) That the ONLY way to precheck for this fatal exception is 
                ''' via Windows API calls, which with DotNET developments is 
                ''' about equivalent to feeding oats and barley to your 
                ''' automobile. 
                    If curDoc.Saved Then
                        'If MS Word didn't throw back our attempt to check the document.Saved 
                        'property, then no dialogs are open and we can work with the document. 
                        'So if we can work with it, if the document.Saved property is True, 
                        'then the user did not CANCEL the dialog (or did but the document ondisk 
                        'is still identical to the document in memory, so it doesn't matter). 
                        'So, we fire the AfterSave Event. 
                        RaiseEvent WordDoc_AfterSave(queue_Docs.Dequeue)
                    End If
                    curDoc = Nothing
                Catch ex As Exception
                    'CODE HERE IF YOU NEED TO CHECK FOR 
                    ' Word.Application BUSY Status. 
                    ' As previously noted, this Try block only exists 
                    ' to bypass fatal exceptions thrown when dialogs are 
                    ' open. I've thoroughly tested this code with 
                    ' multiple documents open in MS Word, with 
                    ' user-saving, auto-saving, click "save", 
                    ' click "save as", close unsaved document and save, 
                    ' and never had a problem - that doesn't mean a 
                    ' problem won't arise. Might be wise to include 
                    ' an event log entry here. 
                    th_SaveCheck.BeginInvoke(th_SaveDone, th_SaveCheck)
                End Try
            End SyncLock
        End If
    End Sub

    ''' The th_AfterSaveDone sub is the target of our AsyncCallback. 
    ''' It serves just one purpose: To restart the background thread 
    ''' when it terminates (finishes processing). 
    ''' Additional code can be added here if needed or desired - 
    ''' just make sure you add it BEFORE the BeginInvoke if you want 
    ''' it performed before it processes again, and be sure to SyncLock 
    ''' if you do any work with the Queue Object. 

    Private Sub th_AfterSaveDone(ByVal ar As System.IAsyncResult)
        th_SaveCheck.BeginInvoke(th_SaveDone, th_SaveCheck)
    End Sub

    ''' Last, the AfterSave EventHandler. 
    ''' The thread delegate fires this event at any time that: 
    ''' BeforeSave has fired, the application is accessible, 
    ''' and the document which BeforeSave fired for returns 
    ''' Document.Saved = True. 

    Private Sub Addin_AfterSave( _
    ByVal Doc As Word.Document) _
    Handles Me.WordDoc_AfterSave

        'Run your event code here. 
        'For this example, it's just a messagebox notifying 
        'that the event fired and providing the name of the document 
        'that was just saved. 

        MsgBox("Document: " & Doc.FullName & " AFTERSAVE Complete!")
    End Sub

Hope this helps everyone who has need of the MS Word AfterSave Event.

Best wishes,

Posted 31 Dec, 2008 17:13:47 Top
Sergey Grischenko

Add-in Express team

Posts: 7228
Joined: 2004-07-05
Hi Andrew.

Thank you very much for the code.
Posted 03 Jan, 2009 08:10:57 Top