等待Excel Interop在C#中退出

时间:2013-12-02 12:56:09

标签: c# excel wait exit

我正在开发一种工具,可以为我自动执行一些操作。其中一个操作是下载excel文件,在其上运行宏,然后邮寄该文件。在某些情况下,我希望工具只运行excel宏,然后在x秒后退出excel并邮寄文件。这一切都很好,因为我可以等待一段固定的时间。但是,在某些情况下,我希望能够检查和更改excel宏检索到的数据。在这种情况下,我想保持excel打开,直到使用手动关闭excel。当我检测到excel不再打开时,我想邮寄该文件。

这最后一个案例给了我一些麻烦。因为我使用excel interlop来打开excel,所以我不能像正常进程一样使用WaitForExit()。当我手动关闭excel时,该进程也会继续在进程资源管理器中运行。

我在互联网上搜索过解决方案,但没有一个能真正起作用。有什么方法可以用简单的方式实现这个目标吗?

更新 的 非常感谢您的回复,这对我很有帮助。等待excel现在正确地工作。我使用了以下代码:

if (Settings.ExitExcel)
{
  System.Threading.Thread.Sleep(Settings.ExcelTimeout * 1000);

  //Close Excel
  excelWorkbook.Close();
  excelApp.Quit();
}
else
{
  Excel.AppEvents_WorkbookBeforeCloseEventHandler Event_BeforeBookClose;

  Event_BeforeBookClose = new Excel.AppEvents_WorkbookBeforeCloseEventHandler(WorkbookBeforeClose);
  excelApp.WorkbookBeforeClose += Event_BeforeBookClose;

  //Wait until excel is closed
  while (!isClosed)
  {
    Thread.Sleep(1000);
  }

  //Show message               
  MessageBox.Show("excel closed");
}

//Clean up excel.
Marshal.FinalReleaseComObject(excelSheets);
Marshal.FinalReleaseComObject(excelWorksheet);
Marshal.FinalReleaseComObject(excelWorkbook);
Marshal.FinalReleaseComObject(excelApp);
excelApp = null;
excelWorkbook = null;
excelWorksheet = null;
excelSheets = null;
GC.Collect();     

现在唯一的问题是excel仍然在流程中继续运行。我没有正确关闭它吗?

2 个答案:

答案 0 :(得分:2)

您可以使用BeforeClose事件执行此操作。基本上,您需要按照以下几点做一些事情:

  1. 创建一个跟踪状态的变量,即Excel是打开还是关闭。
  2. 为WorkbookBeforeClose事件创建事件处理程序。
  3. 在处理程序中,检查工作簿是否已关闭,并相应地更新状态跟踪变量的值。
  4. 如果工作簿已关闭,请执行邮件发送操作。
  5. 代码可以是这样的:

    //Outside main
    private static bool isClosed = false;
    
    ....
    
    //Let's say your Workbook object is named book
    book.WorkbookBeforeClose += new Excel.AppEvents_WorkbookBeforeCloseEventHandler(app_WorkbookBeforeClose);
    
    if(isClosed)
    sendMailMethod();
    
    ...
    
    private static void app_WorkbookBeforeClose(Excel.Workbook wb, ref bool cancel)
        {
            closed = !cancel;
        }
    

    此方法不要求您先杀死进程。但是,您应该释放COM对象并在Excel工作完成后结束该过程。

    编辑:要关闭该过程,请尝试使用Application.Quit()方法。

答案 1 :(得分:0)

如果您还没有找到解决方案,那么以下是我整理的适用于我的东西。

private async void OnOpenWorkSheetImportCommand(object sender, ExecutedRoutedEventArgs e)
{
    try
    {
        if (e.Parameter is IWorkSheetImportViewModel model)
        {
            if (WorksheetDictionaries.OfType<WorksheetDictionary>().FirstOrDefault(a => a.Header.Equals(model.Header)) is WorksheetDictionary worksheetDictionary)
            {
                Type officeType = Type.GetTypeFromProgID("Excel.Application", true);

                if (officeType != null)
                {
                    BeforeWorkbookDictionary = worksheetDictionary.WorkbookDictionary;
                    OpenInteropWorkbookDictionary(worksheetDictionary.WorkbookDictionary, worksheetDictionary, true);
                }
                else
                {
                    throw new ArgumentException("Microsoft Excel is not installed!");
                }
            }
        }
    }
    ...
    ..
    .
}

调用 OpenInteropWorkbookDictionary 是我设置 Microsoft.Office.Interop.Excel

的地方
private void OpenInteropWorkbookDictionary(WorkbookDictionary workbookDictionary, WorksheetDictionary worksheetDictionary, bool activateWorksheet = false)
{
    if (ExcelApplication != null && ExcelApplication.Visible == true)
    {
        ExcelApplication.ActiveWindow.Activate();
        IntPtr handler = FindWindow(null, ExcelApplication.Caption);
        SetForegroundWindow(handler);
    }
    else
    {
        if (ExcelApplication != null) Marshal.FinalReleaseComObject(ExcelApplication);
        if (ExcelWorkbook != null) Marshal.FinalReleaseComObject(ExcelWorkbook);
        if (ExcelWorksheet != null) Marshal.FinalReleaseComObject(ExcelWorksheet);
        if (ExcelWorksheets != null) Marshal.FinalReleaseComObject(ExcelWorksheets);

        ExcelApplication = null;
        ExcelWorkbook = null;
        ExcelWorksheet = null;
        ExcelWorksheets = null;

        ExcelApplication = new Microsoft.Office.Interop.Excel.Application
        {
            // if you want to make excel visible to user, set this property to true, false by default
            Visible = true,
            WindowState = Microsoft.Office.Interop.Excel.XlWindowState.xlMaximized
        };

        ExcelApplication.WindowActivate += new Microsoft.Office.Interop.Excel.AppEvents_WindowActivateEventHandler(OnWindowActivate);
        ExcelApplication.WindowDeactivate += new Microsoft.Office.Interop.Excel.AppEvents_WindowDeactivateEventHandler(OnWindowDeactivate);

        ExcelApplication.WorkbookOpen += new Microsoft.Office.Interop.Excel.AppEvents_WorkbookOpenEventHandler(OnWorkbookOpen);
        ExcelApplication.WorkbookActivate += new Microsoft.Office.Interop.Excel.AppEvents_WorkbookActivateEventHandler(OnWorkbookActivate);
        ExcelApplication.WorkbookDeactivate += new Microsoft.Office.Interop.Excel.AppEvents_WorkbookDeactivateEventHandler(OnWorkbookDeactivate);

        // open an existing workbook
        ExcelWorkbook = ExcelApplication.Workbooks.Open(BeforeWorkbookDictionary.FilePath, 0, false, 5, "", "", false, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "", true, false, 0, true, false, false);
        ExcelWorkbook.BeforeClose += new Microsoft.Office.Interop.Excel.WorkbookEvents_BeforeCloseEventHandler(OnBeforeClose);
        ExcelWorkbook.AfterSave += new Microsoft.Office.Interop.Excel.WorkbookEvents_AfterSaveEventHandler(OnAfterSave);
        ExcelWorkbook.BeforeSave += new Microsoft.Office.Interop.Excel.WorkbookEvents_BeforeSaveEventHandler(OnBeforeSave);

        // SheetEvents
        ExcelWorkbook.SheetChange += new Microsoft.Office.Interop.Excel.WorkbookEvents_SheetChangeEventHandler(OnSheetChange);
        ExcelWorkbook.SheetSelectionChange += new Microsoft.Office.Interop.Excel.WorkbookEvents_SheetSelectionChangeEventHandler(OnSheetSelectionChange);
        ExcelWorkbook.NewSheet += new Microsoft.Office.Interop.Excel.WorkbookEvents_NewSheetEventHandler(OnNewSheet);
        ExcelWorkbook.SheetBeforeDelete += new Microsoft.Office.Interop.Excel.WorkbookEvents_SheetBeforeDeleteEventHandler(OnSheetBeforeDelete);
        ExcelWorkbook.SheetActivate += new Microsoft.Office.Interop.Excel.WorkbookEvents_SheetActivateEventHandler(OnSheetActivate);
        ExcelWorkbook.SheetDeactivate += new Microsoft.Office.Interop.Excel.WorkbookEvents_SheetDeactivateEventHandler(OnSheetDeactivate);

        //// get all sheets in workbook
        ExcelWorksheets = ExcelWorkbook.Worksheets;
    }

    if (activateWorksheet)
    {
        ExcelWorksheet = ExcelWorkbook.Sheets[worksheetDictionary.Name];
        ExcelWorksheet.Activate();
    }
}

要注意:

  1. ExcelWorkbook。关闭之前首先被称为然后
  2. ExcelApplication。 WorkbookDeactivate 在最后关闭 Excel 之前被调用
  3. 因此在ExcelApplication中。 WorkbookDeactivate 是我设置并使用 Process 的地方,等待它自然终止。
  4. 其他:我使用反应性来捕获 OnClose事件
  5. 重要说明::当用户关闭工作簿时,我关闭 Excel

因此在 OnWorkbook停用中:

private async void OnWorkbookDeactivate(Microsoft.Office.Interop.Excel.Workbook Wb)
{
    try
    {
        ProgressController = default(ProgressDialogController);
        if (ExcelApplication != null && ExcelApplication.Visible == true)
        {
            int excelProcessId = -1;
            GetWindowThreadProcessId(new IntPtr(ExcelApplication.Hwnd), ref excelProcessId);
            Process ExcelProc = Process.GetProcessById(excelProcessId);

            if (ExcelProc != null)
            {
                ExitedEventArgs = Observable.FromEventPattern<object, EventArgs>(ExcelProc, MethodParameter.Exited);
                DisposableExited = ExitedEventArgs.Subscribe(evt => OnExitedEvent(evt.Sender, evt.EventArgs));
                ExcelProc.EnableRaisingEvents = true;

                ProgressController = await _dialogCoordinator.ShowProgressAsync(this, string.Format("Workbook {0}", BeforeWorkbookDictionary.ExcelName), "Please wait! Processing any changes...");
                ProgressController.SetIndeterminate();

                ExcelApplication.Quit();

                if (ExcelApplication != null) Marshal.FinalReleaseComObject(ExcelApplication);
                if (ExcelWorkbook != null) Marshal.FinalReleaseComObject(ExcelWorkbook);
                if (ExcelWorksheet != null) Marshal.FinalReleaseComObject(ExcelWorksheet);
                if (ExcelWorksheets != null) Marshal.FinalReleaseComObject(ExcelWorksheets);

                ExcelApplication = null;
                ExcelWorkbook = null;
                ExcelWorksheet = null;
                ExcelWorksheets = null;

                ExcelProc.WaitForExit();
                ExcelProc.Refresh();
            }
        }
    }
    catch (Exception msg)
    {
    ...
    ..
    .
    }
}

我在课堂上设置了几件事,以使所有这些正常工作:

#region DllImports

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowThreadProcessId(IntPtr hwnd, ref int lpdwProcessId);

#endregion

并且:

#region RoutedEventArgs Properties

private IDisposable disposableClosingEvent;
public IDisposable DisposableClosingEvent
{
    get => this.disposableClosingEvent; set => SetProperty(ref disposableClosingEvent, value, nameof(DisposableClosingEvent));
}

private IObservable<EventPattern<object, EventArgs>> exitedEventArgs;
public IObservable<EventPattern<object, EventArgs>> ExitedEventArgs
{
    get => exitedEventArgs; set => SetProperty(ref exitedEventArgs, value, nameof(ExitedEventArgs));
}
private IDisposable disposableExited;
public IDisposable DisposableExited
{
    get => this.disposableExited; set => SetProperty(ref disposableExited, value, nameof(DisposableExited));
}

#endregion

最后 OnExitedEvent 进行最后一分钟的处理。

private async void OnExitedEvent(object sender, EventArgs e)
{
    try
    {
        DisposableExited.Dispose();
        using (SpreadsheetDocument s = SpreadsheetDocument.Open(SelectedWorkbookDictionaryImport.FilePath, false))
        {
            foreach (Sheet workbookSheet in s.WorkbookPart.Workbook.Sheets)
            {
                if (WorkbookDictionaryImports.OfType<WorkbookDictionary>().FirstOrDefault(d => d.ExcelName.Equals(SelectedWorkbookDictionaryImport.ExcelName)) is WorkbookDictionary workbookDictionary)
                {
                    if (workbookDictionary.WorksheetDictionaryItems.OfType<WorksheetDictionary>().FirstOrDefault(d => d.Id.Equals(workbookSheet.Id)) == null)
                    {
                        Application.Current.Dispatcher.Invoke(() =>
                        {
                            WorksheetDictionary worksheetDictionary = new WorksheetDictionary()
                            {
                                WorksheetDictionaryId = Guid.NewGuid().ToString(),
                                IsSelected = false,
                                Id = workbookSheet.Id,
                                SheetId = workbookSheet.SheetId,
                                Name = workbookSheet.Name,
                                Header = workbookSheet.Name,
                                HeaderInfo = GetSheetStateValues(workbookSheet.State),
                                Code = GetSheetCodeValues(workbookSheet.Name.ToString()).ToString(),
                                Description = BeforeWorkbookDictionary.Subject,
                                WorkbookDictionary = BeforeWorkbookDictionary
                            };
                            workbookDictionary.WorksheetDictionaryItems.Add(worksheetDictionary);
                        });
                    }
                }
            }
        }
    }
    catch (Exception msg)
    {
    ...
    ..
    .
    }
    finally
    {
        if (ProgressController != null)
        {
            await ProgressController.CloseAsync();
            ProgressController = default(ProgressDialogController);
        }
    }
}

希望这会帮助您和其他人进行粗略的更改,以满足您的处理需求。