Word AfterSave Event

So, in addition to my last post, one of the more common features I am asked about for Word is an AfterSave event. If you have done much Office Developer work, you will know how handy this would be, if it were only available.Well, in working with a few customers over the years I have developed a class that you can use to detect an After Save event. Hot smile

Here is how it works:

  1. Upon initialization you pass it your Word object.
  2. It attaches to the Before Save Event.
  3. When any save event occurs, it kicks off a thread that loops until the Background Save is complete.
  4. Once the background save is done, it checks to see if the document Saved == true:
  • If Saved == true: then a regular save did occur.
  • If Saved == false: then it had to be an AutoSave

In each case it will fire a unique event:

  • AfterSaveUiEvent
  • AfterSaveEvent
  • AfterAutoSaveEvent

Here is the code to the class:

public class WordSaveHandler
{
    public delegate void AfterSaveDelegate(Word.Document doc, bool isClosed);
    // public events
    public event AfterSaveDelegate AfterUiSaveEvent;
    public event AfterSaveDelegate AfterAutoSaveEvent;
    public event AfterSaveDelegate AfterSaveEvent;

    // module level
    private bool preserveBackgroundSave;
    private Word.Application oWord;

    /// <summary>
    /// CONSTRUCTOR – takes the Word application object to link to.
    /// </summary>
    /// <param name="oApp"></param>
    public WordSaveHandler(Word.Application oApp)
    {
        oWord = oApp;
        // hook to before save
        oApp.DocumentBeforeSave += new
            Word.ApplicationEvents4_DocumentBeforeSaveEventHandler(
                        oApp_DocumentBeforeSave);
    }

    /// <summary>
    /// WORD EVENT – fires before a save event.
    /// </summary>
    /// <param name="Doc"></param>
    /// <param name="SaveAsUI"></param>
    /// <param name="Cancel"></param>
    void oApp_DocumentBeforeSave(Word.Document Doc,
                                 ref bool SaveAsUI, ref bool Cancel)
    {
        // This could mean one of four things:
        // 1) we have the user clicking the save button
        // 2) Another add-in or process firing a resular Document.Save()
        // 3) A Save As from the user so the dialog came up
        // 4) Or an Auto-Save event…
        // so, we will start off by first:
        // 1) Grabbing the current background save flag. We want to force
        //    the save into the background so that Word will behave
        //    asyncronously. Typically, this feature is on by default,
        //    but we do not want to make any assumptions or this code
        //    will fail.
        // 2) Next, we fire off a thread that will keep checking the
        //    BackgroundSaveStatus of Word. And when that flag is OFF
        //    no know we are AFTER the save event…
        preserveBackgroundSave = oWord.Options.BackgroundSave;
        oWord.Options.BackgroundSave = true;
        // kick off a thread and pass in the document object
        bool UiSave = SaveAsUI; // have to do this because the bool from Word
                                // is passed to us as ByRef…
        ThreadStart starter = delegate {
                Handle_WaitForAfterSave(Doc, UiSave); };
        new Thread(starter).Start();
    }

    /// <summary>
    /// This method is the thread call that waits for the same to compelte.
    /// The way we detect the After Save event is to essentially enter into
    /// a loop where we keep checking the background save status. If the
    /// status changes we know the save is compelte and we finish up by
    /// determineing which type of save it was:
    /// 1) UI
    /// 2) Regular
    /// 3) AutoSave
    /// </summary>
    /// <param name="Doc"></param>
    /// <param name="UiSave"></param>
    private void Handle_WaitForAfterSave(Word.Document Doc, bool UiSave)
    {
        try
        {
            // we have a UI save, so we need to get stuck
            // here until the user gets rid of the SaveAs dialog
            if (UiSave)
            {
                while (isBusy(Doc))
                    Thread.Sleep(1);
            }

            // check to see if still saving in the background
            // we will hang here until this changes.
            while (oWord.BackgroundSavingStatus > 0)
                Thread.Sleep(1);
        }
        catch
        {
            oWord.Options.BackgroundSave = preserveBackgroundSave;
            return; // swallow the exception
        }

        try
        {
            // if it is a UI save, the Save As dialog was shown
            // so we fire the after ui save event
            if (UiSave)
            {
                // we need to check to see if the document is
                // saved, because of the user clicked cancel
                // we do not want to fire this event
                try
                {
                    if (Doc.Saved == true)
                        AfterUiSaveEvent(Doc, false);
                }
                catch
                {
                    // DOC is null or invalid. This occurs because the doc
                    // was closed. So we return doc closed and null as the
                    // document…
                    AfterUiSaveEvent(null, true);
                }
            }
            else
            {
                // if the document is still dirty
                // then we know an AutoSave happened
                try
                {
                    if (Doc.Saved == false)
                        AfterAutoSaveEvent(Doc, false); // fire autosave event
                    else
                        AfterSaveEvent(Doc, false); // fire regular save event
                }
                catch
                {
                    // DOC is closed
                    AfterSaveEvent(null, true);
                }
            }
        }
        catch {}
        finally
        {
            // reset and exit thread
            oWord.Options.BackgroundSave = preserveBackgroundSave;
        }
    }

    /// <summary>
    /// Determines if Word is busy – essentially that the File Save
    /// dialog is currently open
    /// </summary>
    /// <param name="oApp"></param>
    /// <returns></returns>
    private bool isBusy(Word.Document oDoc)
    {
        try
        {
            // if we try to access the application property while
            // Word has a dialog open, we will fail
            Word.Application oApp = oDoc.Application;
            return false; // not busy
        }
        catch
        {
            // so, Word is busy and we return true
            return true;
        }
    }
}

And here is how you set it up and attach to it’s events:

private void ThisAddIn_Startup(object sender,
                               System.EventArgs e)
{
    // attach the save handler
    WordSaveHandler wsh = new
        WordSaveHandler(Application);
    wsh.AfterAutoSaveEvent += new
        WordSaveHandler.AfterSaveDelegate(
            wsh_AfterAutoSaveEvent);
    wsh.AfterSaveEvent += new
        WordSaveHandler.AfterSaveDelegate(
            wsh_AfterSaveEvent);
    wsh.AfterUiSaveEvent += new
        WordSaveHandler.AfterSaveDelegate(
            wsh_AfterUiSaveEvent);
}

void wsh_AfterUiSaveEvent(Word.Document doc, bool isClosed)
{
    if (!isClosed)
        MessageBox.Show("After SaveAs Event");
    else
        MessageBox.Show("After Close and SaveAs Event");
}

void wsh_AfterSaveEvent(Word.Document doc, bool isClosed)
{
    if (!isClosed)
        MessageBox.Show("After Save Event");
    else
        MessageBox.Show("After Close and Save Event");
}

void wsh_AfterAutoSaveEvent(Word.Document doc, bool isClosed)
{
    MessageBox.Show("After AutoSave Event");
}

21 thoughts on “Word AfterSave Event”

  1. Hi,
    maybe this one:
    while (isBusy(Doc)) Thread.Sleep(1);
    should always happen, because I had a situation right now, when it failed without.
    There was this ‘conversion warning’ dialog popping up, but it doesn’t come along
    with SaveAsUI=true!

  2. Thanks alot for the code..It is exactly what i was looking for.
    Working perfectly.
    Can you tell me how can i achieve the same functionality in Excel.
    Options.BackgroundSave is not available in excel application object.
    Any alternative you might be aware of?

    Many thanks

  3. subscribe to BeforeSave
    in the event handler do Doc.Save()
    Cancel = true;
    then goes your after save code….

  4. Thanks a lot for the code. Is there a way to retrieve the file location in the wsh_AfterUiSaveEvent if the user close word, but still save it”s document? I try Doc.Path, but it’s not a good idea. It’s seem word has close the doc object. Any idea how I can do this?

  5. Thanks for the quick response. In order to reproduce my issue, try the following.

    1. Open microsoft word.
    2. Type anything in the word document.
    3. Press the window close button (At this moment, the Word_DocumentBeforeClose is call, but, since the file has never been save anywhere, the doc.Path is empty. So, word will open a save dialog)
    4. Save the file anywhere on your machine
    5. Try to retrieve the location specify in the save dialog.

    In the Handle_WaitForAfterSave method, it’s no longer possible to retrieve where the user save his file, since the Doc property seem to be empty.

    Maybe i’m missing something?

    Best regards.

    1. It has been a long time since I looked at this code. I worked with it, I believe in Word 2007. While reviewing your issue, I found a few things that needed to be corrected, especially to get this to work properly in Word 2013 and to be able to catch all scenarios. The answer your question on getting the filename, the easiest way is to attach to the WindowDeactivate event. It is the very last event to fire as a document is going out of memory. From here we can get an accurate filename. I save it in a private string and then added a property to the class called ClosedFilename. On a close event where the resultant value returned for isClosed == true, I can then query that property and get the filename.

      I have updated the entire class in a new post:
      http://davecra.wordpress.com/2013/04/26/updated-word-after-save-event/

      Please let me know if this helps.

    1. Hi Dave,

      So are you saying that for me to check if Word is currently running a AutoSave – all I would need to do is check the UiSave?

      if (UiSave)
      {
      // autosaving event
      else
      // regular save event
      }

      I am trying to find a way to determine, for certain that an AutoSave is in progress – the best solution I have found to date is using [WordApp.WordBasic.IsAutoSaveEvent].

      1. The WordBasic command was added to the product a few years ago (maybe 4 now) for exactly that purpose. So that is what I would use now versus this method. I think I blogged about it a while back. If not I will make sure to do so.

      2. Thanks for the reply Dave, I was under the impression that the WordBasic command was really old and no longer advised (no more support etc.). Could you please share the blog link if you have one?

      3. Actually, this was added to the Object Model very recently ~2012. While WordBasic itself is no longer supported and WordBasic is also not even advertised, this particular command was added to the WordBasic set so that the Word team would not have to update the overall Object Model. There is a whole rabbit hole there… Suffice to say, it is supported and was added recently just for this very reason. I detail it all here: http://davecra.com/2013/06/12/update-autorecovery-save-autosave-fires-off-the-documentbeforesave-event-in-word/

    1. Unfortunately, I do not believe this can be done from a Mac. The VBA interface is the only development interface and it does not allow true multi-threading and the ability to fire an event after another event. The closest thing there is to that is the Application.OnTime() event. https://msdn.microsoft.com/en-us/library/office/ff820816.aspx. I am not sure if that is supported on the Mac, but that might be a step you can take from VBA if it does work.

  6. Very nice work, thanks!! One question – In Word, I have an open document – I navigate in the ‘Save As’ dialog to a directory and select an exisiting file. When I now click ‘Save’ instead of ‘Cancel’, I get the message if I want to overwrite/merge the exisitng document. Is it possible to intercept the ‘Save’ event in the ‘Save As’ Dialog so that I can change the file name of the open document, suppressing the overwrite/merge message? Any suggestions are greatly appreciated!

    1. There is no way to catch the Save event from the Word / Built-in Save As Dialog button. If you need to catch that event, then you will need set the Cancel = true on the Save event and display your own System.Windows.Forms.SaveFileDialog. There you can catch the Save or Cancel button and appropriately handle the next operation.

Leave a Reply to ShaneCancel reply