0

I am writing an application in C# that changes data in an excel worksheet. VBA isn't an option because the version of office that is installed on the clients desktop is Microsoft Office 2010 Starter Edition which doesn't support VBA (none of the starter editions do). The application is using the excel interop library.

When I start the application it checks to see if the excel workbook that is to be modified is open and if it is open it notifies the user and then quits. This part is working as expected. The check isn't working if the user opens the excel file for some reason after starting the application and then trying to save their work from inside the application. In that case any modifications from the application are lost without any error notification. If you need to see more of the code to answer the entire project is in GitHub.

I've tried changing CheckExcelWorkBookOpen from a static class to a class that gets instantiated every time it is used, just in case the list of open workbooks was being stored in the excel interop library, this did not help.

The code that works in the application start up is:

CheckExcelWorkBookOpen testOpen = new CheckExcelWorkBookOpen();
testOpen.TestAndThrowIfOpen(Preferences.ExcelWorkBookFullFileSpec);

The code is also called any time the application attempts to open the file either for input or output, this doesn't work:

    private void StartExcelOpenWorkbook()
    {
        if (xlApp != null)
        {
            return;
        }

        CheckExcelWorkBookOpen testOpen = new CheckExcelWorkBookOpen();
        testOpen.TestAndThrowIfOpen(WorkbookName);
        xlApp = new Excel.Application();
        xlApp.Visible = false;
        xlApp.DisplayAlerts = false;

        xlWorkbook = xlApp.Workbooks.Open(WorkbookName);
    }

Current Code

using System;
using Excel = Microsoft.Office.Interop.Excel;

namespace TenantRosterAutomation
{
    public class CheckExcelWorkBookOpen
    {
        // Check if there is any instance of excel open using the workbook.
        public static bool IsOpen(string workBook)
        {
            Excel.Application TestOnly = null;
            bool isOpened = true;
            // There are 2 possible exceptions here, GetActiveObject will throw
            // an exception if no instance of excel is running, and
            // workbooks.get_Item throws an exception if the sheetname isn't found.
            // Both of these exceptions indicate that the workbook isn't open.
            try
            {
                TestOnly = (Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
                int lastSlash = workBook.LastIndexOf('\\');
                string fileNameOnly = workBook.Substring(lastSlash + 1);
                TestOnly.Workbooks.get_Item(fileNameOnly);
                TestOnly = null;
            }
            catch (Exception)
            {
                isOpened = false;
                if (TestOnly != null)
                {
                    TestOnly = null;
                }
            }
            return isOpened;
        }

        // Common error message to use when the excel file is op in another app.
        public string ReportOpen()
        {
            string alreadyOpen = "The excel workbook " +
                Globals.Preferences.ExcelWorkBookFullFileSpec +
                    " is alread open in another application. \n" +
                    "Please save your changes in the other application and close the " +
                    "workbook and then try this operation again or restart this application.";

            return alreadyOpen;
        }

        public void TestAndThrowIfOpen(string workBook)
        {
            if (IsOpen(workBook))
            {
                AlreadyOpenInExcelException alreadOpen =
                    new AlreadyOpenInExcelException(ReportOpen());
                throw alreadOpen;
            }
        }
    }
}

This code is now included in a question on code review.

pacmaninbw
  • 439
  • 1
  • 10
  • 22
  • Marshal.GetActiveObject() is not a reliable indication that Excel is already running. It only registers its active object when the user activates another process. Consider Process.GetProcessesByName() as an alternative. – Hans Passant Jan 11 '22 at 20:36
  • @HansPassant It is okay if excel is running, what isn't okay is if the excel workbook is open in an instance of excel. – pacmaninbw Jan 12 '22 at 00:31
  • Purely based on the `isOpen` perhaps "An Is File Open" function is what you're really looking for: https://stackoverflow.com/a/11060322/495455 – Jeremy Thompson Jan 13 '22 at 22:51

1 Answers1

0

I got the above code to work by ensuring that any excel process started by the application was killed after the task was complete. The following code is added to my ExcelInterface module. The Dispose(bool) function already existed but did not kill the process:

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                if (xlWorkbook != null)
                {
                    xlWorkbook.Close();
                    xlWorkbook = null;
                }

                if (xlApp != null)
                {
                    xlApp.Quit();
                    xlApp = null;
                    Process xlProcess = Process.GetProcessById(ExcelProcessId);
                    if (xlProcess != null)
                    {
                        xlProcess.Kill();
                    }
                }
            }
            disposed = true;
        }
    }

    [DllImport("user32.dll")]
    static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);
    private int GetExcelProcessID(Excel.Application excelApp)
    {
        int processId;
        GetWindowThreadProcessId(excelApp.Hwnd, out processId);
        return processId;
    }

    private void StartExcelOpenWorkbook()
    {
        if (xlApp != null)
        {
            return;
        }

        CheckExcelWorkBookOpen testOpen = new CheckExcelWorkBookOpen();
        testOpen.TestAndThrowIfOpen(WorkbookName);
        xlApp = new Excel.Application();
        xlApp.Visible = false;
        xlApp.DisplayAlerts = false;

        xlWorkbook = xlApp.Workbooks.Open(WorkbookName);

        ExcelProcessId = GetExcelProcessID(xlApp);
    }
pacmaninbw
  • 439
  • 1
  • 10
  • 22