2016-04-15 81 views
1

對不起,這個問題的長度,在過去我被問過以前包括了我試過的所有問題。VSTO Word post保存事件

我正在寫一個Word加載項,並且需要對使用Word對象模型無法實現的文檔進行更改。因此,在將文檔保存到磁盤後,我需要捕獲該事件,關閉文件,執行我需要執行的操作並重新打開它。 (我知道,不夠高雅,但這就是我必須使用的。)

Word在保存之前和保存之前沒有保存事件。我通過創建另一個線程並使用COM的IMessageFilter(不是來自System.Windows.Forms)來處理COM重試調用,或者在主線程中發回消息以便我可以在保存後執行代碼,從而在線模擬保存事件後的技巧。但是這不起作用,因爲如果由於用戶試圖關閉文檔而導致文件被保存,我無法在我的「回調」方法中獲取文件名,因爲Word.Document對象已被刪除。

所以我試着顯式調用將自己保存在我的BeforeSave事件處理函數中並返回Cancel = true。當用戶選擇保存,或者他們曾經保存到磁盤時,這很好用。但是,如果用戶在不保存的情況下關閉了一個新文檔,然後選擇「是」來確定是否要保存,則Word在我從BeforeSave事件返回後已經處理完保存後會顯示另一個「另存爲」對話框,即使我在BeforeSave事件處理程序中設置了Cancel = true。

所以然後我試着做與BeforeClose事件類似的東西。我處理關閉並保存自己,然後從我的事件處理程序返回Cancel = true。但是這樣做會阻止在用戶試圖關閉應用程序時關閉多個文檔。

我甚至嘗試過處理WM_CLOSE,但是這導致了與上面類似的問題。

任何人都可以提供解決方案嗎?

回答

2

我偶然碰到了this,我想它可能會做你想做的。如果它消失了,那麼這裏有一個副本。


當我寫我的第一Word AfterSave Event項,它是專爲Word 2007中,並且是 - 事實證明 - 不是包羅萬象的。所以我在這裏更新了它(感謝趕上Pat Lemm)。

當文檔關閉時,您永遠無法訪問保存的文件名。所以,我在這裏更新的代碼,現在適用於所有條件,並在Word 2013年已通過測試

這裏是它如何工作的:

  1. 在初始化你傳遞你的Word對象。
  2. 它附着在Before Save事件之前。
  3. 當發生任何保存事件時,它會啓動循環線程直到後臺保存完成。
  4. 一旦後臺保存完成後,它會檢查,看是否該文件保存==真:

    • 如果保存==真:那麼一個普通的保存沒有發生。
    • 如果保存==假:那麼它必須是一個自動保存

在每一種情況下,它會觸發一個獨特的事件:

  • 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. 

}