【问题标题】:Prevent Excel from quitting防止 Excel 退出
【发布时间】:2011-04-06 22:33:16
【问题描述】:

我错过了 Excel.Application.QuitExcel.Application.BeforeQuit 事件。 有人知道模仿这些事件的解决方法吗?

我通过 COM Interop 从 C# WinForms 应用程序访问 Excel。给定一个Excel.Application 对象,我该怎么做:

  1. 最好防止 Excel 退出?
  2. 如果无法做到这一点,我如何才能在 Excel 退出时至少通知

请注意:由于我有一个对Excel.Application 的 COM 引用,当 Excel 处于“退出”状态时,Excel 进程不会退出 "由用户。虽然这听起来很矛盾,但事实就是如此。 “退出”是指用户点击窗口右上角的“退出”或“十字按钮”。窗口关闭,文件被卸载,加载项被卸载,除了我不知道的东西之外,Excel 所做的任何事情。但是我仍然可以使用Application 对象来“恢复”该过程并使 Excel 再次可见,尽管加载项随后丢失了,而且我不确定还有什么处于未定义状态。

为了摆脱这个问题,我想一开始就取消退出(想想BeforeQuitCancel = true,如果它存在的话),或者至少在 Excel 退出时得到通知,所以我可以释放COM对象,让进程真正退出,下次再需要Excel的时候就知道需要先启动了。

不幸的是,这是一个恶性循环:只要 Excel 运行,我就需要 COM 对象。所以我不能在 Excel 退出之前处理它们。另一方面,只要 COM 对象存在,即使 Excel 假装退出进程也不会退出,所以我不能等待进程退出事件或类似事件。

我有一种不愉快的感觉,我要把头撞在砖墙上......

【问题讨论】:

  • 请用枕头,众所周知砖块很硬:)
  • 是否可以监控 Process.Exit 事件,如果用户手动关闭 Excel 则以隐藏状态重新启动?
  • @Marko:你的意思是我根本不应该使用 Excel? :-) / :-(
  • @Mikael:我刚刚意识到:不,这不是一个选项,因为进程根本不会退出。它挂在后台,因为我仍然有 COM 引用,我什至可以使用它来打开另一个工作簿并使应用程序再次可见 - 只是,它在那个时间点已经卸载了加载项。但这就是为什么我没有注意到用户退出应用程序的原因:COM 引用仍然有效...
  • 悬赏谁为这两个问题中的任何一个提供了有效且可靠的解决方案。

标签: c# excel com interop excel-2007


【解决方案1】:

请注意,我还没有尝试过。

创建一个工作簿,其中包含BeforeClose 上的代码。
例如

Option Explicit

Private Sub Workbook_BeforeClose(Cancel As Boolean)
    Cancel = True
End Sub

打开此工作簿以及您拥有的其他工作簿,并且不必隐藏它(如果整个应用程序不可见)。

因此,如果您尝试退出 excel 实例,它将强制关闭此隐藏的工作簿,这将引发其 BeforeClose 事件,您可以编写代码来阻止它关闭。

请注意,以上代码是在 VB6 (VBA) 中,需要转换成 c#。
如果您发现转换有任何困难,请发表评论。

如果你想隐藏一个工作簿,你可以这样做

Workbooks("my workbook").Windows(1).Visible = False 

注意:工作簿有一个 Windows 集合。上面的代码试图隐藏第一个窗口。
我不知道,一个工作簿可以有多个窗口吗?如果有,怎么做?

【讨论】:

  • 这种方法听起来很有前途,也可以用来通知 Quit,因为我可以通过观察工作簿集合来注意到工作簿关闭事件 (stackoverflow.com/questions/2767439/…)
  • 嗨,沙卡尔佩什。我试过了,效果很好!授予这种肮脏的方法让我感到有些不舒服,但实际上这是唯一真正实现所讨论内容的答案。
  • @chiccodoro:是的,它很脏。如果您碰巧遇到更好的方法,请随时发布。这肯定会帮助其他人尝试破解:)。也感谢您接受答案。
  • 希望这能完美运行,但尽管我没有方便的 url 供您参考关闭。无论如何,就像我说的希望它能解决你的问题。
【解决方案2】:

有一篇 C++ 知识库文章 How to automate Excel and then know the user closed it。我还没有将它移植到 C# 中,但可能工作量不大。

【讨论】:

  • 如果您使用Marshal.ReleaseComObject 正确清理 COMObjects,就像 Otaku 在他的回答中指出的那样,该过程将退出 onQuit。这个问题进一步描述了释放 Office 互操作对象:stackoverflow.com/questions/158706/… 我手头没有 Excel-2007 版本,所以我不确定是否有用于 ApplicationQuit 的 EventHandler。 Process 解决方案似乎是您的最佳选择。
  • 我很抱歉我太苛刻了。只是沮丧地想尽我最大的努力制定一个精确的问题,150 个代表的赏金切片,然后看到一个答案被最高投票,这并不能解决我的问题,并且可能会阻止其他用户调查这个线程。 - 我意识到误解来自我并没有说我在 Excel 运行的整个过程中都需要对象来对事件做出反应,因为如果 Excel 没有退出,我想保留 Application 对象以便下次重用它需要提高性能。不过我可能会将您的建议与西蒙的变体结合起来。
  • @chiccodoro:没问题,在阅读所有编辑的基础上,我更清楚地看到了你想要什么。我已经使用 MSFT 关于该主题的唯一已知解决方案编辑了上述内容。
  • @Otaku:会看看,谢谢。我删除了我的第一条评论,因为现在它已经没有任何意义了。
  • @chiccodoro:除非你也想要它们,否则它不会释放它们。代码所做的只是清理事件处理程序OnSeexlquit 中的内容。在那个事件处理程序中,你可以做任何你喜欢的事情,比如保存你的文件等。它告诉你的是 Excel 将要退出(X 是点击退出),所以关闭正确的东西并清理.这回答了你上面的#2,而不是你的#1。
【解决方案3】:

这当然是一个 hack,但您不能使用带有 WH_SHELL 或 WH_CBT 的 Windows SetWindowsHookEx API 来获得有关 Excel 主窗口被破坏的通知吗?

注意:它肯定有安全隐患,即需要一些管理员权限来执行跨进程魔法。

【讨论】:

  • 嗨,西蒙。你有一些观点。相反,我也可以选择观察 Excel.Application.Visible 属性。到目前为止,我不想使用它,因为假设 VBA 宏出于任何原因设置了Visible = false,它会将其识别为退出。不过,结合 Otaku 的提议,它可能是有希望的。
  • 这就是白盒测试应用程序所做的。他们使用 Windows 钩子 API 调用来获取父窗口并通过鼠标/键盘自动化控制它。但是,您会发现我认为这种方法存在问题......例如在进程终止后卸载资源,在这种情况下,您可能仍然会遇到插件被禁用的问题。
【解决方案4】:

您在此处尝试解决的问题不会通过监视程序退出来解决。 在您说我没有回答您的问题之前,您在问题中声明即使在用户退出 excel 后您也可以恢复 excel。因此 excel.exe 进程仍在运行,因为您有一个 .net 对象,其中包含对 excel.application 的 com 互操作引用。

所以你有三个选择:

  1. 避免用户退出 Excel。 正如您所说,让 Excel 无法退出,但是我不知道有一种方法可以阻止用户导致 Excel 退出,因此您已经正确地注意到卸载您的和任何其他插件等。请记住微软专门设计通过这种方式的用户交互,他们希望用户能够关闭他们的应用程序。您的插件需要能够处理此问题,如果不能,我会说这是您的插件而不是 Excel 的问题。我可能错了,因为我对您的应用需求了解不够。

  2. 在用户退出之前清理所有非托管资源。 您需要做的是在用户手动退出 Excel 之前清理对所有 Excel 和 Office 非托管资源的引用,这样当他们退出时,您的应用程序代码不会留下任何剩余资源,这些资源现在指向不再存在的 excel 实例加载了插件等。步骤 (a) 应在您不再需要特定资源时立即执行,或者甚至在将其重用于其他东西(即 Excel.Range 类型)时执行,而步骤 (b) 应较少使用,但如果它一个获胜的应用程序而不是一个插件,可能更频繁,这完全取决于您的应用程序和在用户可能完成关闭任务之前您拥有的机会窗口(时间)。显然,使用插件,您可以将其放入关闭事件中,或任意放入您的代码中。

    一个。正如 Otaku 所指出的,对每个使用后为 != null 的非托管资源使用 Marshal.FinalReleaseCOMObject。

        if (ComObject != null)
        {
            Marshal.FinalReleaseComObject(ComObject);
            ComObject = null;
        }
    

    b.对 COM 资源使用 GC 清理模式。

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        GC.WaitForPendingFinalizers();
    
  3. 重新加载插件 如果由于该任务的复杂性、时间限制(尽管我建议这样做)您对完全跟踪和卸载所有非托管资源不感兴趣,您可以考虑重新加载您可能已经在您的环境中知道的任何必需的插件.这仅在您控制环境时才有效。 有手动加载 Excel 和 COM 插件的技术。至于其他东西,我不知道,但如果您在启动/XLSTART 目录中使用 XLL 或 XLT,这可能是可能的,但无论如何都会加载。

【讨论】:

  • 可能还有另一个“hack”选项,但这需要您强制用户在单独的 Excel.exe 实例中打开他们的工作簿,然后关闭对该实例的引用。
  • 您好匿名类型,感谢您的精心回复!您的“介绍”很好地说明了我一直试图解释的内容。关于 1,我还没有开发加载项,但它们是处理我的应用程序管理的文件所必需的。 Excel 在退出时卸载所有加载项,即如果我恢复该 Excel,它们将不再可用。至于3,我尝试重新加载加载项,但没有奏效,结果容易出错。关于 2:问题是我需要对象只要 Excel 正在运行,因为我必须观察一些事件,例如BeforeSave 事件,并说 Cancel = true...
  • 好的,我知道您需要观察某些事件的状态,例如 BeforeSave 等,但是如果我假设您有一个(Excel.exe 外部)应用程序正在运行,该应用程序正在监视这些事件每个 Excel 实例,如果 Excel.exe 的实例已关闭,您能否不仅将这些事件的当前状态序列化并将其写入 config/xml 文件?你说你应该有一个关于 Excel 中事件的鸡蛋场景,但这并不是一个真正可行的程序场景。在某些时候,您需要能够处理您的用户从 Excel.exe 脱机的事实
  • 根据您在问题中定义的要求,我想我对您的程序场景的了解还不够。我认为您应该尽可能详细说明您对这些事件所做的事情(例如 BeforeSave)..
【解决方案5】:

为什么不直接执行System.Diagnostics.Process.Start(@"SomeWorkbook.xlsx"); 以确保 Excel 已启动。如果它已经启动,则不会创建新进程。

【讨论】:

  • 因为 (1) 我需要通过 COM 与工作簿通信,以及 (2) 我需要以编程方式使用密码打开工作簿,因此用户不必输入。跨度>
  • @chiccodoro - 您可以 (1) 仍然通过 COM 进行通信 - 使用 Process.Start 只是确保 Excel 正在运行。 (2) 您在问题中没有提及密码,但您确实说过可以通过Process.Start 启动它。也许您需要向我们提供更多细节?
  • 抱歉让您感到困惑,我会相应地更新我的问题。
  • 已完全改写我的问题以缩小范围。你的答案现在看起来有点离题了,很抱歉。
【解决方案6】:

为什么不直接使用 Application.ApplicationExit 事件来知道它何时关闭?

【讨论】:

  • 没有这样的事件,或者至少我找不到。您指的是哪个版本的 Excel?
猜你喜欢
  • 2017-02-11
  • 2017-10-23
  • 2010-10-09
  • 2012-07-26
  • 2018-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多