Created
October 24, 2022 19:16
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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