0
votes

I'm developing a VSTO add-in for Microsoft Word and I'm having difficulty with handling the BeforeSave event. I wish to edit the document each time the user saves but the BeforeSave event appears to be triggering in a certain scenarios when the user is not saving the document.

Assuming a user opens a blank Word document, enters some text and then tries to close the document. The dialogenter image description here

displays. If the user clicks "Don't Save", the BeforeSave event still triggers (the event only triggers after the Save Changes dialog closes). Is there anyway to detect the difference between the use clicking Don't Save and clicking save or else to prevent the Before Save event from triggering in this scenario?

The event handler is being assigned using Word.ApplicationEvents4_DocumentBeforeSaveEventHandler and the event handler signature is Application_DocumentBeforeSave(Word.Document doc, ref bool saveAsUI, ref bool cancel)

Any help would be great thanks

2
I don't think you can tell the difference between "Don't Save" and "Save" - this is a defect in Word. Better to handle the document Close event - this lets you catch the process before the dialog above is shown. You can then use your own save dialog and bypass Word's entirely. With that method you would have to call .Save yourself, of course.J...

2 Answers

1
votes

The Word object model doesn't provide any event property for that. The best what you can do in that case are listed below:

  1. If the document has never been saved before, you may check out the Document.Saved property.
  2. Cancel the default action in the Application.DocumentBeforeSave event handler by setting the Cancel parameter to true. So, you can show your own dialog which mimics the default built-in one where you may handle every action made by the user.
0
votes

The solution that worked for me in the end is based on the article at https://theofficecontext.com/2013/04/26/updated-word-after-save-event/

For reference, I'm working with Office 2019 and below is the code that worked for me - a slightly simplified version to filter out the user clicking Don't Save and trigger a Post Save event.

    using System.Threading;
    using Word = Microsoft.Office.Interop.Word;

    /// <summary>
    /// The word save handler.
    /// </summary>
    public class WordSaveHandler
    {
        public delegate void AfterSaveDelegate(Word.Document doc, bool isClosed);

        // public events
        public event AfterSaveDelegate AfterSaveEvent;

        // module level
        private bool preserveBackgroundSave;
        private Word.Application oWord;
        private string closedFilename = string.Empty;

        /// <summary>
        /// Initializes a new instance of the <see cref="WordSaveHandler"/> class.
        /// CONSTRUCTOR  takes the Word application object to link to.
        /// </summary>
        /// <param name="oApp">Word Application.</param>
        public WordSaveHandler(Word.Application oApp)
        {
            this.oWord = oApp;

            // hook to before save
            this.oWord.DocumentBeforeSave += this.OWord_DocumentBeforeSave;
            this.oWord.WindowDeactivate += this.OWord_WindowDeactivate;
        }

        /// <summary>
        /// Gets public property to get the name of the file
        /// that was closed and saved.
        /// </summary>
        public string ClosedFilename
        {
            get
            {
                return this.closedFilename;
            }
        }

        /// <summary>
        /// WORD EVENT  fires before a save event.
        /// </summary>
        /// <param name="doc"></param>
        /// <param name="saveAsUI"></param>
        /// <param name="cancel"></param>
        private void OWord_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
            this.preserveBackgroundSave = this.oWord.Options.BackgroundSave;
            this.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
            new Thread(() =>
            {
                this.Handle_WaitForAfterSave(doc, uiSave);
            }).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 complete and we finish up by
        /// determineing which type of save it was:
        /// 1) UI
        /// 2) Regular
        /// 3) AutoSave.
        /// </summary>
        /// <param name="doc">Document being saved.</param>
        /// <param name="uiSave">Whether a SaveAs UI is displayed.</param>
        private void Handle_WaitForAfterSave(Word.Document doc, bool uiSave)
        {
            bool docSaved = false;
            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 (this.IsBusy())
                    {
                        Thread.Sleep(1);
                    }
                }

                docSaved = doc.Saved;

                // check to see if still saving in the background
                // we will hang here until this changes.
                while (this.oWord.BackgroundSavingStatus > 0)
                {
                    Thread.Sleep(1);
                }
            }
            catch (ThreadAbortException)
            {
                // we will get a thread abort exception when Word
                // is in the process of closing, so we will
                // check to see if we were in a UI situation
                // or not
                if (uiSave)
                {
                    this.AfterSaveEvent(null, true);
                    return;
                }
                else
                {
                    // new, close, don't save - docSaved = FALSE
                    // open close don't save - docSaved = FALSE
                    // open close save - docSaved = TRUE
                    if (docSaved)
                    {
                        this.AfterSaveEvent(null, true);
                    }

                    return;
                }
            }
            catch
            {
                this.oWord.Options.BackgroundSave = this.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)
                        {
                            this.AfterSaveEvent(doc, false);
                            // new, save
                            // new, save as
                            // open save as
                            // open, turn on autosave
                            // new, turn on autosave
                        }
                    }
                    catch
                    {
                        // DOC is null or invalid. This occurs because the doc
                        // was closed. So we return doc closed and null as the
                        // document
                        this.AfterSaveEvent(null, true);
                        // -- new, close, save
                    }
                }
                else
                {
                    // if the document is still dirty
                    // then we know an AutoSave happened
                    try
                    {

                        this.AfterSaveEvent(doc, false); // fire regular save event
                                                         // open, save
                                                         // open, autosave
                    }
                    catch
                    {
                        // DOC is closed
                        this.AfterSaveEvent(null, true);
                    }
                }
            }
            catch { }
            finally
            {
                // reset and exit thread
                this.oWord.Options.BackgroundSave = this.preserveBackgroundSave;
            }
        }

        /// <summary>
        /// WORD EVENT – Window Deactivate
        /// Fires just before we close the document and it
        /// is the last moment to get the filename.
        /// </summary>
        /// <param name="doc"></param>
        /// <param name="wn"></param>
        private void OWord_WindowDeactivate(Word.Document doc, Word.Window wn)
        {
            this.closedFilename = doc.FullName;
        }

        /// <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()
        {
            try
            {
                // if we try to access the application property while
                // Word has a dialog open, we will fail
                object o = this.oWord.ActiveDocument.Application;
                return false; // not busy
            }
            catch
            {
                // so, Word is busy and we return true
                return true;
            }
        }
    }