Excel自动化:缺少关闭事件

时间:2010-05-04 17:19:42

标签: c# excel events interop

另外,大家好,

我正在通过C#中的Interop进行Excel自动化,我希望在工作簿关闭时得到通知。但是,工作簿上没有Close事件,也没有应用程序上的Quit事件。

以前有人这样做过吗? 如何编写一段对正在关闭的工作簿做出反应的代码(仅在工作簿真正关闭时才执行)?理想情况下应该在之后执行关闭工作簿,因此我可以依赖该文件来反映所有更改。

到目前为止我发现的详细信息:

有一个BeforeClose()事件,但是如果有未保存的更改,则会在询问用户是否保存它们之前引发此事件,所以目前我可以处理该事件,我没有最终文件和我不能释放COM对象,这两件事我都需要/做。我甚至不知道工作簿是否会被关闭,因为用户可能会选择中止关闭。

然后有一个BeforeSave()事件。因此,如果用户选择“是”来保存未保存的更改,则在 BeforeClose()之后执行BeforeSave()。但是,如果用户选择“中止”,然后点击“文件 - >保存”,则执行完全相同的事件顺序。此外,如果用户选择“否”,则根本不执行BeforeSave()。只要用户没有点击任何这些选项,情况就同样如此。

4 个答案:

答案 0 :(得分:4)

我使用类似轮询的方法创建了一个hack,它可以工作:

鉴于要观察的工作簿,我创建了一个线程,它定期尝试在工作簿集合中查找该工作簿。

(DisposableCom类是我目前properly cleanup COM objects的解决方案。)

Excel.Application app = wbWorkbook.Application;
string sWorkbookName = wbWorkbook.Name;

Thread overseeWorkbooksThread = new Thread(new ThreadStart(
    delegate()
    {
        bool bOpened = false;

        Excel.Workbooks wbsWorkbooks = app.Workbooks;
        using (new DisposableCom<Excel.Workbooks>(wbsWorkbooks))
        {
            while (true)
            {
                Thread.Sleep(1000);

                if (wbsWorkbooks.ContainsWorkbookProperly(sWorkbookName))
                    bOpened = true;
                else
                    if (bOpened)
                        // Workbook was open, so it has been closed.
                        break;
                    else
                    {
                        // Workbook simply not finished opening, do nothing
                    }
            }

            // Workbook closed
            RunTheCodeToBeRunAfterWorkbookIsClosed();
        }
    }));

overseeWorkbooksThread.Start();

“ContainsWorkbookProperly”扩展方法如下所示:

public static bool ContainsWorkbookProperly(this Excel.Workbooks excelWbs,
    string sWorkbookName)
{
    Excel.Workbook wbTemp = null;
    try
        wbTemp = excelWbs.Item(sWorkbookName);
    catch (Exception)
    {
        // ignore
    }

    if (wbTemp != null)
    {
        new DisposableCom<Excel.Workbook>(wbTemp).Dispose();
        return true;
    }

    return false;
}

如果有更简单或更好的解决方案,我仍然会感兴趣。

答案 1 :(得分:3)

这不是我的代码,但这对我有用:

https://gist.github.com/jmangelo/301884

复制粘贴:

[element0, element1, ..., elementN]
new Array(element0, element1[, ...[, elementN]])
new Array(arrayLength)

当我使用它时,我将其从返回工作簿的名称更改为对工作簿的引用。

答案 2 :(得分:0)

在工作簿上安排SyncContext操作。停用。关闭工作簿时以及另一个工作簿获得焦点时,都会引发此事件。

通常,在Deactivate处理程序中,您无法检查工作簿是否已关闭或只是失去焦点,但是可以在SyncContext上加入一个操作,使其在事件发生后立即执行。在该操作中,您可以检查工作簿是否仍然存在,并在不存在的情况下执行代码。

这是一个例子:

// put a syncContext instance somewhere you can reach it
static SynchronizationContext syncContext = SynchronizationContext.Current ?? new System.Windows.Forms.WindowsFormsSynchronizationContext();

// subscribe to workbook deactivate
workbook.Deactivate += workbook_Deactivate;


[DebuggerHidden]
private void workbook_Deactivate()
{
    // here, the workbook is still alive, but we can schedule
    // an action via the SyncContext which will execute 
    // right after the deactivate event is completed. At that 
    // point, the workbook instance (RCW) will no longer be usable
    // meaning that the workbook has been closed

    syncContext.Post(x =>
    {
        try
        {
            // will throw if workbook is gone
            workbook.Path.ToString();
        }
        catch
        {
            // handle workbook closed
        }
    }, null);
}

答案 3 :(得分:-1)

你能同时使用这两个活动吗? On BeforeClose()设置一个标志,然后BeforeSave()查看是否设置了标志。但是,如果触发BeforeClose()而没有BeforeSave(),则需要一种方法来重置它。不确定是否还有其他方法可以帮助解决这个问题。

编辑:看起来你已经用“完全相同的事件顺序执行”来覆盖这个。但是,如果你能找到一种方法来重置它(另一个“取消”事件?)它可能会有效。