How to do a custom action after an event in Microsoft Office Application using VSTO

Over this blog I will be trying to address a common concern I have been hearing from developers. If you are familiar with Excel object model, we do have an Workbook.AfterSave event, however there are no AfterPrint event if you wish to do some custom action after print is done. Similarly in case of Microsoft Word there are no exposed DocumentAfterSave or DocumentAfterPrint. These are design limitations for Word and Excel Object Model.

WorkArounds:

There are couple of workarounds which can be utilized here:

WorkAround 1 ( Using Application.OnTime)

     Application.OnTime schedules a procedure to be run at a specified time in the future (either at a specific time of day or after a specific amount of time has passed). 

Here is a sample code for a Word Customization on a Macro Enabled Document. It is vital to note that Application.Ontime will only be able to call a VBA method loaded. 

So this workaround is pretty much limited to a Macro Enabled Customization or An Excel Application Level Add-in with a macro add-in(xlam add-in)supplementing

 ThisDocument.cs

 using System;
 using System.Collections.Generic;
 using System.Data;
 using System.Linq;
 using System.Text;
 using System.Windows.Forms;
 using System.Xml.Linq;
 using Microsoft.Office.Tools.Word;
 using Microsoft.VisualStudio.Tools.Applications.Runtime;
 using Office = Microsoft.Office.Core;
 using Word = Microsoft.Office.Interop.Word;
namespace ApplicationOnTime_Customization_Sample
{
public partial class ThisDocument
{
private void ThisDocument_Startup(object sender, System.EventArgs e)
{
ThisApplication.DocumentBeforeSave += ThisApplication_DocumentBeforeSave;
ThisApplication.DocumentBeforePrint += ThisApplication_DocumentBeforePrint;
}

void ThisApplication_DocumentBeforePrint(Word.Document Doc, ref bool Cancel)
{
Application.OnTime(System.DateTime.Now.AddSeconds(1), "AfterEventVBA");
}

void ThisApplication_DocumentBeforeSave(Word.Document Doc, ref bool SaveAsUI, ref bool Cancel)
{
Application.OnTime(System.DateTime.Now.AddSeconds(1), "AfterEventVBA");
}

private void ThisDocument_Shutdown(object sender, System.EventArgs e)
{
}

#region VSTO Designer generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisDocument_Startup);
this.Shutdown += new System.EventHandler(ThisDocument_Shutdown);
}

#endregion
}
}

ThisDocument.AfterEventVBA

 
Sub AfterEventVBA()

  Application.Selection.Range.Text = "This is an after Event"

End Sub

 

WorkAround 2 ( By Spawning a Thread in Office Application's Context)

 Another way to workaround the design limitation is by the following approach

 1.       Start a new thread 

 2.       Use Control.Invoke to make sure the OM call happens on the main thread. Office is a Single Threaded application and its important we have OM calls happening in the same context as Office Main Thread

ThisAddin.CS

 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Xml.Linq;
 using Word = Microsoft.Office.Interop.Word;
 using Office = Microsoft.Office.Core;
 using Microsoft.Office.Tools.Word;
 delegate void doAction();
 namespace SampleWordAdd_In
 {
 public partial class ThisAddIn
 {
 System.Windows.Forms.Control ctrl;
 private void ThisAddIn_Startup(object sender, System.EventArgs e)
 {
 Globals.ThisAddIn.Application.DocumentBeforeSave += Application_DocumentBeforeSave;
 }
void Application_DocumentBeforeSave(Word.Document Doc, ref bool SaveAsUI, ref bool Cancel)
{
//Let's Avoid showing SaveAsUI for demo purposes.
//Note: If we show SaveAsUi. We need enough sleep time for thread to compensate for UserAction
SaveAsUI = false;

ctrl = new System.Windows.Forms.Control();
//Have Form control created so that we can do Control.Invoke(Delegate)
IntPtr hndl = ctrl.Handle;

System.Threading.Thread t = new System.Threading.Thread(doIt);
t.Start();

}
public void doIt()
{
System.Threading.Thread.Sleep(5000);
//Wait on Word to Finish Loading events.. Please make sure we have enough sleep time
doAction myAction = default(doAction);
myAction = new doAction(AfterSaveActionsMethod);
ctrl.Invoke(myAction);
}
public void AfterSaveActionsMethod()
{

Globals.ThisAddIn.Application.Selection.Range.Text = "Text from After Event";

}

private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
}

#region VSTO generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}

#endregion
}
}
 Note: We have to make sure there is enough sleep time for Office Event finish loading, else we may end up in Access Violations or Thread abort exceptions

Disclaimer:

This sample is provided "AS IS" with no warranty or support from Microsoft. It is a demonstration, provided for informational purposes only, and has not been rigorously tested with all environments and does not contain error handling. It is up to you to make it "production ready" if you use it in any development solution.