我正在为 Microsoft Word 开发 VSTO 加载项,但在处理 BeforeSave 事件时遇到困难。我希望在用户每次保存时编辑文档,但在某些情况下,当用户未保存文档时,BeforeSave 事件似乎会被触发。
假设用户打开一个空白Word文档,输入一些文本,然后尝试关闭该文档。对话框
显示。如果用户单击“不保存”,BeforeSave 事件仍会触发(该事件仅在“保存更改”对话框关闭后触发)。无论如何,是否可以检测单击“不保存”和单击“保存”之间的区别,或者防止在这种情况下触发“保存前”事件?
使用 Word.ApplicationEvents4_DocumentBeforeSaveEventHandler 分配事件处理程序 事件处理程序签名是 Application_DocumentBeforeSave(Word.Document doc, ref bool saveAsUI, ref bool cancel)
任何帮助都会非常感谢
Word 对象模型不为此提供任何事件属性。在这种情况下,您可以采取的最佳措施如下:
Cancel
参数设置为 true
来取消 Application.DocumentBeforeSave事件处理程序中的默认操作。因此,您可以显示自己的对话框,该对话框模仿默认的内置对话框,您可以在其中处理用户所做的每个操作。
最终对我有用的解决方案基于https://theofficecontext.com/2013/04/26/updated-word-after-save-event/
的文章作为参考,我正在使用 Office 2019,下面是对我有用的代码 - 一个稍微简化的版本,用于过滤掉单击“不保存”并触发“保存后”事件的用户。
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;
}
}
}