VSTO Word发布保存事件

tde*_*may 3 c# events multithreading vsto

很抱歉,我在过去曾要求我提供在问这类问题时尝试过的所有内容。

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

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

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

因此,我尝试使用BeforeClose事件进行类似的操作。我处理关闭并保存自己,然后从事件处理程序返回Cancel = true。但是这样做会阻止用户尝试关闭应用程序时试图关闭多个文档。

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

谁能提供解决方案?

Chr*_*ris 5

我前一段时间遇到了这个问题,我认为它可能会做您想要的。这是那里的东西的副本,以防万一它消失了。


当我编写我的第一个Word AfterSave Event条目时,它是为Word 2007设计的,事实证明,这并非一帆风顺。因此,我在这里已对其进行了更新(感谢您的帮助,请联系Pat Lemm)。

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

下面是它的工作原理:

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

    • 如果Saved == true:则进行常规保存。
    • 如果Saved == false:则必须为自动保存

在每种情况下,它将触发一个唯一事件:

  • 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是设置和附加事件的方法:

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.

}
Run Code Online (Sandbox Code Playgroud)