Skip to content

Instantly share code, notes, and snippets.

@VitekBed
Created October 24, 2022 19:16
Show Gist options
  • Save VitekBed/a2e1582ab9500e9513b39b85c53a6e89 to your computer and use it in GitHub Desktop.
Save VitekBed/a2e1582ab9500e9513b39b85c53a6e89 to your computer and use it in GitHub Desktop.
Excel VSTO: Workaround for dinstinguishing Save, AutoSave and SaveUI and detection for WorbookClose
#region Workaround pro Close (https://gist.github.com/jmangelo/301884 s rozšířením o this.Workbook)
/// <summary>
/// Zajišťuje sledování dokumentu a vyvolání události po zavření
/// </summary>
public sealed class WorkbookClosedMonitor
{
internal class CloseRequestInfo
{
public CloseRequestInfo(string name, Excel.Workbook workbook, int count)
{
this.WorkbookName = name;
this.WorkbookCount = count;
this.Workbook = workbook;
}
public string WorkbookName { get; set; }
public int WorkbookCount { get; set; }
public Excel.Workbook Workbook { get; set; }
}
public WorkbookClosedMonitor(Excel.Application application)
{
if (application == null)
{
throw new ArgumentNullException("application");
}
this.Application = application;
this.Application.WorkbookActivate += Application_WorkbookActivate;
this.Application.WorkbookBeforeClose += Application_WorkbookBeforeClose;
this.Application.WorkbookDeactivate += Application_WorkbookDeactivate;
}
public event EventHandler<WorkbookClosedEventArgs> WorkbookClosed;
public Excel.Application Application { get; private set; }
private CloseRequestInfo PendingRequest { get; set; }
private void Application_WorkbookDeactivate(Excel.Workbook wb)
{
if (this.Application.Workbooks.Count == 1)
{
// With only one workbook available deactivating means it will be closed
this.PendingRequest = null;
this.OnWorkbookClosed(new WorkbookClosedEventArgs(wb.Name, wb));
}
}
private void Application_WorkbookBeforeClose(Excel.Workbook wb, ref bool cancel)
{
if (!cancel)
{
this.PendingRequest = new CloseRequestInfo(
wb.Name,
wb,
this.Application.Workbooks.Count);
}
}
private void Application_WorkbookActivate(Excel.Workbook wb)
{
// A workbook was closed if a request is pending and the workbook count decreased
bool wasWorkbookClosed = true
&& this.PendingRequest != null
&& this.Application.Workbooks.Count < this.PendingRequest.WorkbookCount;
if (wasWorkbookClosed)
{
var args = new WorkbookClosedEventArgs(this.PendingRequest.WorkbookName, this.PendingRequest.Workbook);
this.PendingRequest = null;
this.OnWorkbookClosed(args);
}
else
{
this.PendingRequest = null;
}
}
private void OnWorkbookClosed(WorkbookClosedEventArgs e)
{
var handler = this.WorkbookClosed;
if (handler != null)
{
handler(this, e);
}
}
}
public sealed class WorkbookClosedEventArgs : EventArgs
{
internal WorkbookClosedEventArgs(string name, Excel.Workbook workbook)
{
this.Name = name;
this.Workbook = workbook;
}
public string Name { get; private set; }
public Excel.Workbook Workbook { get; private set; }
}
#endregion
#region Workaround pro SaveAfter (https://theofficecontext.com/2011/05/05/word-aftersave-event/ upraveno)
/// <summary>
/// Zajišťuje sledování dokumentu a vyvolání událostí po uložení
/// </summary>
public class WordSaveHandler
{
public delegate void AfterSaveDelegate(Excel.Workbook doc, bool isClosed);
// public events
/// <summary>
/// Vyvolá se v případě, kdy dojde k zobrazení UI ukládání. Obvykle při prvním uložení nebo při <em>Uložit jako</em>.
/// </summary>
public event AfterSaveDelegate AfterUiSaveEvent;
public event AfterSaveDelegate AfterAutoSaveEvent;
public event AfterSaveDelegate AfterSaveEvent;
// module level
private bool preserveBackgroundSave;
private Excel.Application oWord;
/// <summary>
/// CONSTRUCTOR – takes the Word application object to link to.
/// </summary>
/// <param name="oApp"></param>
public WordSaveHandler(Excel.Application oApp)
{
oWord = oApp;
// hook to before save
//oApp.DocumentBeforeSave += new
// Word.ApplicationEvents4_DocumentBeforeSaveEventHandler(
// oApp_DocumentBeforeSave);
oApp.WorkbookBeforeSave += oApp_DocumentBeforeSave;
}
/// <summary>
/// WORD EVENT – fires before a save event.
/// </summary>
/// <param name="Doc"></param>
/// <param name="SaveAsUI"></param>
/// <param name="Cancel"></param>
void oApp_DocumentBeforeSave(Excel.Workbook Doc,
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…
ThreadStart starter = delegate {
Handle_WaitForAfterSave(Doc, UiSave);
};
new Thread(starter).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(Excel.Workbook 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(Doc))
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(100); //no, raději tu nějaký Sleep nechám, možná je to důležité a 100 ms si nikdo nevšimne
}
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>
/// Determines if Word is busy – essentially that the File Save
/// dialog is currently open
/// </summary>
/// <param name="oApp"></param>
/// <returns></returns>
private bool isBusy(Excel.Workbook oDoc)
{
try
{
// if we try to access the application property while
// Word has a dialog open, we will fail
Excel.Application oApp = oDoc.Application;
return false; // not busy
}
catch
{
// so, Word is busy and we return true
return true;
}
}
}
#endregion
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment