3

In my C# interop addin created using VSTO, I subscribe to the Document.BeforeSave event. However, another MS Word addin is active on our client's computer also subscribing to this exact same event.

The third party addin cancels the default Word SaveAsDialog and shows its own custom SaveAsDialog (it is a DMS dialog). Our use-case is that we want to show our own SaveAsDialog and override the behavior from the third party.

The calling order of the Document.BeforeSave event seem arbitrary. Sometimes our subscriber is called first, sometimes the third-party addin is called first.

Is there a way to reliably cancel the third-party call?

Edit:

I have tried the following code:

private void ThisAddIn_Startup(object sender, System.EventArgs e) {
    Application.DocumentOpen += Application_DocumentOpen;
}

void Application_DocumentOpen(Word.Document Doc) {
    Application.DocumentBeforeSave += Application_DocumentBeforeSave;
    var handler = new Word.ApplicationEvents2_DocumentBeforeSaveEventHandler(Application_DocumentBeforeSave);
    MulticastDelegate multicastDelegate = handler;
    var subscribers = handler.GetInvocationList();
    for (int i = 0; i < handler.GetInvocationList().Count(); i++) {
        Delegate.RemoveAll(multicastDelegate, subscribers[i]);
    }
    Application.DocumentBeforeSave += Application_DocumentBeforeSave2;
    Application.DocumentBeforeSave += Application_DocumentBeforeSave;
}

void Application_DocumentBeforeSave(Word.Document Doc, ref bool SaveAsUI, ref bool Cancel) {
    MessageBox.Show("Save 1");
}

void Application_DocumentBeforeSave2(Word.Document Doc, ref bool SaveAsUI, ref bool Cancel) {
    MessageBox.Show("Save 2");
}

This does not give the intended effect of having 2 messageboxes showing "2" then "1" consecutively. Instead, it displays "1", "2", "1".

Edit 2: This code works as intended:

public class HasEvents {
    public delegate void WoeiHandler();
    public event WoeiHandler Woei;

    public void OnWoei() {
        Woei();
    }
}

public class Program {

    static void Main(string[] args) {
        HasEvents hasEvents = new HasEvents();
        hasEvents.Woei += () => Console.WriteLine("ShortVersion");
        hasEvents.Woei += Program_Woei;
        hasEvents.OnWoei();
        BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
        FieldInfo field = hasEvents.GetType().GetField("Woei", bindingFlags);
        MulticastDelegate multicastDelegate = (MulticastDelegate)field.GetValue(hasEvents);
        Delegate[] subscribers = multicastDelegate.GetInvocationList();

        Delegate current = multicastDelegate;
        for (int i = 0; i < subscribers.Length; i++) {
            current = Delegate.RemoveAll(current, subscribers[i]);
        }
        Delegate[] newSubscriptions = new Delegate[subscribers.Length + 1];
        newSubscriptions[0] = new HasEvents.WoeiHandler(Program_Woei_First);
        Array.Copy(subscribers, 0, newSubscriptions, 1, subscribers.Length);
        current = Delegate.Combine(newSubscriptions);
        field.SetValue(hasEvents, current);
        hasEvents.OnWoei();
    }

    static void Program_Woei() {
        Console.WriteLine("Program_Woei");
    }

    static void Program_Woei_First() {
        Console.WriteLine("First!");
    }

}
Ruudjah
  • 807
  • 7
  • 27
  • +1 I have experienced the pain of conflicting VSTO add-ins. In one case http://stackoverflow.com/questions/10528775/how-to-add-a-menu-item-to-excel-2010-cell-context-menu-old-code-doesnt-work another add-in was removing my menu!! Is there anyway you can unsubscribe the other add-ins event handler to the `Document.BeforeSave`?? Worth a google... – Jeremy Thompson Nov 01 '14 at 07:37
  • Does the `Delegate.RemoveAll(multicastDelegate, subscribers[i]);` allow you to override (ie remove) the behavior from the third party addin? – Jeremy Thompson Nov 05 '14 at 22:35
  • I have removed the first event handler using standard .NET/C# reflection (Delegate.RemoveAll()). Thus, this one should not be attached and called. – Ruudjah Nov 06 '14 at 08:46
  • Cool, you might want to see if there is any property you can use to confirm its the particular AddIn with the Dialog you're trying to skip and leave all other events subscribed. That way if the company need/get another AddIn in the future for some completely different purpose (eg temporary backup copies) your code doesn't cause a conflict like I experienced with my missing menu situation. Please post your final solution as an answer. Cheers!! – Jeremy Thompson Nov 06 '14 at 09:44
  • Well, it doesn't work, I get "1", "2", "1", so it didnt remove the first event handler. The `Delegate.RemoveAll()` call probably doesn't work since it's talking to a COM object underneath the .NET object shill. – Ruudjah Nov 06 '14 at 11:40
  • See my update its not possible:( – Jeremy Thompson Nov 06 '14 at 13:03

1 Answers1

0

UPDATE: It does not seem possible as per Jon Skeet (except maybe in WPF but certainly not in Winforms or Add-Ins)

The only way I see it being done is by changing the LoadBehavior registry key of the other AddIn.

One other experiment is to try implementing System.ComponentModel.Component and follow this answer.


Disclaimer, I haven't tried this but seeing my comment about another add-in resetting my menu means both add-ins have access to the same instance of the application I figure if the event signatures are the same then unsubscribing using the invocation list should work. Have a play around with this approach, I will try to test this if I get time to setup an environment.

Here is a go I had with Excel:

Microsoft.Office.Tools.Excel.Workbook vstoDoc = Globals.Factory.GetVstoObject(this.Application.ActiveWorkbook);
WorkbookEvents_BeforeSaveEventHandler handler = vstoDoc.BeforeSave;

for (int i = 0; i < handler.GetInvocationList().Length; i++)
{
  try
  {
  vstoDoc.BeforeSave -= new Microsoft.Office.Tools.Excel.SaveEventHandler(ThisDocument_BeforeSave);
  }
catch { } //may raise exception if a handler is not attached.
}

Word was trickier (again this compiles but I didn't test it, here is where I got the idea from and I am not sure if it will work):

var handler = new ApplicationEvents2_DocumentBeforeSaveEventHandler(Target); 

for (int i = 0; i < handler.GetInvocationList().Length; i++)
{
    try
    {
        vstoDoc.GetVstoObject().BeforeSave -= new Microsoft.Office.Tools.Word.SaveEventHandler((o, args) => { });
    }
    catch { } //may raise exception if a handler is not attached.
}

}

private void Target(Document doc, ref bool saveAsUi, ref bool cancel)
{
throw new NotImplementedException();
}

Edit: I'm pulling my hair out as to why this same Office Object Model doesn't work in word but does compile in Excel:

Microsoft.Office.Tools.Word.Document vstoDoc = Globals.Factory.GetVstoObject(this.Application.ActiveDocument);
ApplicationEvents2_DocumentBeforeSaveEventHandler handler = vstoDoc.BeforeSave;
for (int i = 0; i < handler.GetInvocationList().Length; i++)
{
    try
    {
        vstoDoc.BeforeSave -= new Microsoft.Office.Tools.Word.SaveEventHandler(ThisDocument_BeforeSave);
    }
    catch
    {
    } //may raise exception if a handler is not attached.
}

Anyway the idea/Hack/etc was that once you have unsubscribed all other add-ins to listen to the BeforeSave event, then you could assign your AddIn's BeforeSave event so its the only one that fires.

Note: You need to target .Net 4.0 as the Factory in Globals.Factory isn't available in 3.5.

Community
  • 1
  • 1
Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321
  • It is possible. See my updated question. Also note the pre assumption of Jon Skeet: "At least, not with reflection" – Ruudjah Nov 06 '14 at 13:59