在C#Word Interop中调用订单Document.BeforeSave

本文关键字:Document BeforeSave 调用 C#Word Interop | 更新日期: 2023-09-27 18:28:07

在使用VSTO创建的C#互操作插件中,我订阅了Document.BeforeSave事件。然而,另一个MS Word插件在我们客户的计算机上活动,也订阅了完全相同的事件。

第三方加载项取消了默认的Word SaveAsDialog,并显示其自己的自定义SaveAs dialog(它是DMS对话框)。我们的用例是,我们希望显示自己的SaveAsDialog并覆盖来自第三方的行为。

Document.BeforeSave事件的调用顺序似乎是任意的。有时我们的订阅者被首先调用,有时第三方插件被首先调用。

有没有办法可靠地取消第三方通话?

编辑:

我已经尝试了以下代码:

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");
}

这并不能产生两个消息框依次显示"2"answers"1"的预期效果。相反,它显示"1"、"2"、"1"。

编辑2:此代码按预期工作:

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!");
    }
}

在C#Word Interop中调用订单Document.BeforeSave

更新:根据Jon Skeet的说法,这似乎是不可能的(除了可能在WPF中,但肯定不是在Winforms或Add-Ins中)

我看到的唯一方法是更改其他加载项的LoadBehavior注册表项。

另一个实验是尝试实现System.ComponentModel.Component并遵循这个答案。


免责声明,我还没有尝试过,但看到我对另一个加载项重置菜单的评论意味着两个加载项都可以访问应用程序的同一实例我认为如果事件签名相同,那么使用调用列表取消订阅应该可以。试试这种方法,如果我有时间设置一个环境,我会尝试测试一下。

这是我使用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更棘手(再次编译,但我没有测试它,这是我从得到这个想法的地方,我不确定它是否有效):

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();
}

编辑:我很清楚为什么这个Office对象模型在word中不起作用,但在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.
}

无论如何,Hack/etc的想法是,一旦你取消订阅所有其他加载项以收听BeforeSave事件,那么你就可以分配你的加载项的BeforeSave活动,这样它就是唯一触发的事件

注意:您需要将.Net 4.0作为Globals中的Factory。Factory在3.5中不可用。