【问题标题】:VSTO Word post save eventVSTO Word 后保存事件
【发布时间】:2016-04-15 20:48:41
【问题描述】:

对不起,这里的篇幅很长,在过去,我曾被要求包括我在提出这类问题时尝试过的所有内容。

我正在编写一个 Word 加载项,并且需要对文档进行更改,而我使用 Word 对象模型无法实现。因此,在文档保存到磁盘后,我需要捕获该事件,关闭文件,做我需要做的事情并重新打开它。 (我知道,不优雅,但这是我必须使用的。)

Word 有保存前和关闭前,但没有保存后事件。我在网上找到了一些技巧,通过创建另一个线程并使用 COM 的 IMessageFilter(不是来自 System.Windows.Forms)来处理 COM 重试调用,或者在主线程上发回消息,以便我可以在之后执行代码它被保存了。但这不起作用,因为如果由于用户尝试关闭文档而保存了文件,我无法在“回调”方法中获取文件名,因为 Word.Document 对象已被删除。

所以我尝试在我的 BeforeSave 事件处理程序中显式调用 Save 并返回 Cancel = true。当用户选择保存,或者他们曾经保存到磁盘时,这非常有用。但是,如果用户在不保存的情况下关闭了一个新文档,然后选择“是”来决定是否要保存,那么在我从 BeforeSave 事件返回后处理完保存后,Word 会显示另一个“另存为”对话框,即使我在我的 BeforeSave 事件处理程序中设置了 Cancel = true。

然后我尝试对 BeforeClose 事件做类似的事情。我处理关闭并保存自己,然后从我的事件处理程序返回 Cancel = true。但是这样做会阻止 word 在用户尝试关闭应用程序时尝试关闭多个文档。

我什至尝试过处理 WM_CLOSE,但这会导致与上述类似的问题。

谁能提供解决方案?

【问题讨论】:

    标签: c# multithreading events vsto


    【解决方案1】:

    不久前我遇到了this,我认为它可以满足您的需求。这是那里的副本,以防它消失。


    当我编写我的第一个 Word AfterSave Event 条目时,它是为 Word 2007 设计的,而且 - 事实证明 - 并不是一个包罗万象的东西。所以我在这里更新了它(感谢 Pat Lemm 的收获)。

    关闭文档后,您永远无法访问已保存的文件名。所以,我在这里更新了代码,它现在可以在所有条件下工作,并且已经在 Word 2013 中进行了测试。

    这是它的工作原理:

    1. 初始化时,您将 Word 对象传递给它。
    2. 它附加到保存前事件。
    3. 当任何保存事件发生时,它会启动一个循环直到后台保存完成的线程。
    4. 后台保存完成后,它会检查文档 Saved == true:

      • 如果 Saved == true:则确实发生了常规保存。
      • 如果 Saved == false:那么它必须是 AutoSave

    在每种情况下,它都会触发一个独特的事件:

    • AfterSaveUiEvent
    • AfterSaveEvent
    • AfterAutoSaveEvent

    此外,如果正在保存的文档也正在关闭,我们会在退出时捕获 WindowDeactivate 事件中的文件名。调用者现在可以访问它(如下例所示),以获取已关闭文档的完整文件名。

    这是类的代码:

    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;
        string closedFilename = string.Empty;
    
        /// <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
            oWord.DocumentBeforeSave += oWord_DocumentBeforeSave;
            oWord.WindowDeactivate += oWord_WindowDeactivate;
        }
    
        /// <summary>
        /// Public property to get the name of the file
        /// that was closed and saved
        /// </summary>
        public string ClosedFilename
        {
            get
            {
                return closedFilename;
            }
        }
    
        /// <summary>
        /// WORD EVENT  fires before a save event.
        /// </summary>
        /// <param name="Doc"></param>
        /// <param name="SaveAsUI"></param>
        /// <param name="Cancel"></param>
        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
            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
            new Thread(() =>
            {
                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 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())
                        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 (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)
                {
                    AfterUiSaveEvent(null, true);
                }
                else
                {
                    AfterSaveEvent(null, true);
                }
            }
            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>
        /// 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>
        void oWord_WindowDeactivate(Word.Document Doc, Word.Window Wn)
        {
            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 = oWord.ActiveDocument.Application;
                return false; // not busy
            }
            catch
            {
                // so, Word is busy and we return true
                return true;
            }
        }
    }
    

    这是你如何设置它并附加到它的事件:

    public partial class ThisAddIn
    {
        WordSaveHandler wsh = null;
        private void ThisAddIn_Startup(object sender,
                                        System.EventArgs e)
        {
            // attach the save handler
            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. The filname was: " + wsh.ClosedFilename);
        }
    
        void wsh_AfterSaveEvent(Word.Document doc, bool isClosed)
        {
            if (!isClosed)
                MessageBox.Show("After Save Event");
            else
                MessageBox.Show("After Close and Save Event. The filname was: " + wsh.ClosedFilename);
        }
    
        void wsh_AfterAutoSaveEvent(Word.Document doc, bool isClosed)
        {
            MessageBox.Show("After AutoSave Event");
        }
    
        // etc.
    
    }
    

    【讨论】:

      猜你喜欢
      • 2018-08-21
      • 1970-01-01
      • 1970-01-01
      • 2022-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多