2016-04-15 10 views
1

Sorry für die Länge dieser, In der Vergangenheit wurde ich gebeten, alles, was ich versucht habe, wenn Sie diese Art von Fragen gestellt haben.VSTO Word Beitrag Ereignis speichern

Ich schreibe ein Word-Add-In und muss Änderungen am Dokument vornehmen, die ich mit dem Word-Objektmodell nicht erreichen kann. Nachdem das Dokument auf dem Datenträger gespeichert wurde, muss ich daher dieses Ereignis erfassen, die Datei schließen, tun, was ich tun muss, und es erneut öffnen. (Ich weiß, nicht elegant, aber das ist, woran ich arbeiten muss.)

Word hat vor dem Speichern und vor dem Schließen, aber kein Ereignis nach dem Speichern. Ich habe online Tricks gefunden, um ein After-Save-Event zu simulieren, indem ich einen anderen Thread erstelle und COMs IMessageFilter (nicht von System.Windows.Forms) verwende, um COM Retry-Aufrufe zu verarbeiten oder eine Nachricht zurück auf den Haupt-Thread zu schreiben, damit ich Code nach dem Speichern ausführen kann . Aber das funktioniert nicht, da, wenn die Datei gespeichert wird, weil der Benutzer versucht, das Dokument zu schließen, ich den Dateinamen in meiner "Callback" -Methode nicht abrufen kann, da das Word.Document-Objekt bereits gelöscht wurde.

So versuchte ich explizit rufen Sie mich in meinem BeforeSave-Ereignishandler speichern und zurück Cancel = True. Das funktioniert sehr gut, wenn der Benutzer das Speichern auswählt oder wenn er einmal auf der Festplatte gespeichert wurde. Wenn der Benutzer jedoch ein neues Dokument ohne Speichern schließt und dann "Ja" auswählt, ob er speichern möchte, zeigt Word einen anderen "SaveAs" -Dialog an, nachdem ich die Speicherung bereits durchgeführt habe, obwohl ich vom BeforeSave-Ereignis zurückgekehrt bin In meinem BeforeSave-Ereignishandler habe ich Cancel = true gesetzt.

Also habe ich versucht, etwas ähnliches mit dem BeforeClose-Ereignis zu tun. Ich handle mit dem Schließen und speichere mich selbst und dann Cancel = true von meinem Event-Handler zurück. Dies verhindert jedoch, dass versucht wird, mehrere Dokumente zu schließen, wenn der Benutzer versucht, die Anwendung herunterzufahren.

Ich habe sogar versucht, WM_CLOSE, aber das führte zu ähnlichen Problemen wie oben.

Kann jemand eine Lösung anbieten?

Antwort

2

Ich bin vor einer Weile auf this gestoßen, ich denke, es kann tun, was Sie wollen. Hier ist eine Kopie von dem, was für den Fall, dass es jemals verschwindet.


Als ich meinen ersten Word AfterSave Event Eintrag schrieb, war es für Word 2007, und wurde - wie sich herausstellt - alle nicht einen Haken. Also habe ich es hier aktualisiert (Danke für den Fang an Pat Lemm).

Wenn das Dokument geschlossen wurde, haben Sie nie auf den gespeicherten Dateinamen zugegriffen. So habe ich den Code hier aktualisiert und es funktioniert nun in allen Bedingungen und hat in Word 2013

Hier getestet worden ist, wie es funktioniert:

  1. Nach der Initialisierung Sie Ihr Word-Objekt übergeben.
  2. Es wird an das Before Save Event angehängt.
  3. Wenn ein Speicherereignis auftritt, wird ein Thread gestartet, der so lange wiederholt wird, bis die Hintergrundspeicherung abgeschlossen ist.
  4. Sobald der Hintergrund speichern getan wird, überprüft es, ob das Dokument gespeichert == true:

    • Wenn Saved == true: dann regelmäßig speichern trat.
    • Wenn Gespeichert == false: dann hatte es eine Autosave

es ein einmaliges Ereignis wird ausgelöst, in jedem Fall sein:

  • AfterSaveUiEvent
  • AfterSaveEvent
  • AfterAutoSaveEvent

Wenn das zu speichernde Dokument ebenfalls geschlossen wird, fangen wir den Dateinamen des WindowDeactivate-Ereignisses ab. Auf diesen kann nun der Aufrufer zugreifen (wie Sie im folgenden Beispiel sehen können), um den vollständigen Dateinamen des geschlossenen Dokuments zu erhalten. Hier

ist der Code zur Klasse:

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; 
     } 
    } 
} 

Und hier ist, wie Sie es einrichten und heften sich an es Eventss:

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. 

}