Closing a particular excel file when multiple excel files are opened - c#

I am trying to close an excel file named TestReport.xlsx using the below code. It is working when only one excel process is running but when I have multiple excel windows open, the MainWindowTitle changes and the code is not killing the desired excel process.
Process[] plist = Process.GetProcessesByName("Excel",".");
foreach(Process p in plist)
{
if (p.MainWindowTitle.Contains("TestReport.xlsx") && p.ProcessName == "EXCEL")
{
p.Kill();
}
}

The explanation you are giving in your question is not accurate enough. You must distinguish two cases:
Case 1: You have more than one Excel process each with one workbook. That means that e.g. all your excel workbooks each run in an own hosting excel window. You achieve this when e.g right-clicking on the excel icon, getting an empty excel workbook and loading the workbook you want into that process. In this case your approach works, as every excel process has its own title naming the workbook file name. The requested process is killed when iterating through the excel processes.
Case 2, which is the more "normal" case when working with excel, and to which you probably refer to: You have one excel process hosting more than one workbook, each possibly with several worksheets. In that case Excel acts as you describe and changes its window title (Multiple document interface).
In case 2, when there is only one single excel process present, you can close the workbook in the following way, using COM Interop:
private void button1_Click(object sender, EventArgs e)
{
CloseExcelWorkbook("TestReport.xlsx");
}
//put the following abbreviation to the "using" block: using Excel = Microsoft.Office.Interop.Excel;
internal void CloseExcelWorkbook(string workbookName)
{
try
{
Process[] plist = Process.GetProcessesByName("Excel", ".");
if (plist.Length > 1)
throw new Exception("More than one Excel process running.");
else if (plist.Length == 0)
throw new Exception("No Excel process running.");
Object obj = Marshal.GetActiveObject("Excel.Application");
Excel.Application excelAppl = (Excel.Application)obj;
Excel.Workbooks workbooks = excelAppl.Workbooks;
foreach (Excel.Workbook wkbk in workbooks )
{
if (wkbk.Name == workbookName)
wkbk.Close();
}
//dispose
//workbooks.Close(); //this would close all workbooks
GC.Collect();
GC.WaitForPendingFinalizers();
if (workbooks != null)
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(workbooks);
//excelAppl.Quit(); //would close the excel application
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(excelAppl);
GC.Collect();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
This approach works in case you have only ONE Excel process running. When there are more than one present, the situation is more complicated as you must get access to all Excel processes. For a discussion for that case, see here.
Another point to observe is to release excel objects properly to avoid stale excel objects. See there.
If you omit the "dispose" activities, it may happen that on closing Excel and continuing to run your app the Excel process runs silently on, as an inspection in Task Manager shows.

Related

System.AccessViolationException when using Excel.Worksheet.Copy

We have a VSTO addin for Excel. The main functionality creates reports that are used to generate workbooks. When I run a batch of reports, I get a System.AccessViolationException when using Excel.Worksheet.Copy, which also crashes Excel. Here's how I recreate it:
1) Open and run report #1 with a single parameter which creates one workbook. We close the workbook.
2) Open and run the same report with several parameters. This create 5 workbooks but crashes when creating the second, but ONLY if we have run the first single output report (see step 1). If we remove the report from step 1 from the batch, this creates all 5 workbooks without error.
I've checked to make sure that the sheet we are copying is from the workbook is open, and is not referencing the first report. In fact, we close the first one so I know that it's not. Again, this ONLY happens if we have the report in step one, which it does not access at all, so how could that be affecting a sheet from a completely different workbook?
This doesn't even finish out my try/catch so that I can get more info. It simply blows up Excel and I have to restart.
UPDATE:
Here's the basic code:
function void ReplaceSheets(Dictionary<Excel.Worksheet, IReportSheet> sheetReports)
{
List<string> oldNames = new List<string>(sheetReports.Count);
foreach (Excel.Worksheet oldSheet in sheetReports.Keys)
{
Excel.Worksheet veryHiddenSheet = null;
Excel.Worksheet newSheet = null;
try
{
string sheetName = oldSheet.Name;
veryHiddenSheet = WorkbookHelper.FindSheet(this.DocumentView, MakeHiddenSheetName(sheetName, "--VH--"));
veryHiddenSheet.Visible = Excel.XlSheetVisibility.xlSheetVisible; //Sheet has to be visible to get the copy to work correctly.
veryHiddenSheet.Copy(this.DocumentView.Sheets[1], Type.Missing);//This is where it crashes
newSheet = (Excel.Worksheet)this.DocumentView.Sheets[1]; //Get Copied sheet
/* do other stuff here*/
}
finally
{
veryHiddenSheet = null;
newSheet = null;
}
}
}
I never found a way in VSTO to "fix" this. I switched code to NetOffice, and I was able to get some better error message. Excel/Com was not releasing the memory attached to the spreadsheets. I rebuilt the reports from blank 2010 spreadsheets and it took care of it. I think it was a corrupted 2007 spreadsheet that may have occured on converting to 2010 or something like that. I recommend NetOffice over VSTO because the exception handling is far superior, and you have access to the source code, but it does have it's quirks. (You'll need to pay attention to loading order for taskpanes.)

Open multiple excel files in WebBrowser, only the last one gets activated

This question has been asked by others several years ago but no answers yet (https://go4answers.webhost4life.com/Example/excel-web-browser-control-locks-other-206723.aspx), we have the similar issue and need the solution, so I paste here. Note we are using System.Windows.Forms.WebBrowser, this is different from his question:
background: In order to automate and embed Excel in a windows form application I've used the webBrowser control.
I am able to navigate to the Excel file without a problem.To navigate I am using
System.Windows.Forms.WebBrowser WebBrowserExcel;
this.WebBrowserExcel.Navigate(filename,false);
After navigating to the Excel file, I am querying the running Object table to attach to the workBook and then manipulate the cells in the workbook.
public Workbook GetActiveWorkbook(string xlfile) {
IRunningObjectTable prot=null;
IEnumMoniker pmonkenum=null;
try {
//return m_Workbook;
IntPtr pfetched = IntPtr.Zero;
// Query the running object table (ROT)
if (GetRunningObjectTable(0, out prot) != 0 || prot == null) return null;
prot.EnumRunning(out pmonkenum);
pmonkenum.Reset();
IMoniker[] monikers = new IMoniker[1];
while (pmonkenum.Next(1, monikers, pfetched) == 0)
{
IBindCtx pctx; string filepathname;
CreateBindCtx(0, out pctx);
// Get the name of the file
monikers[0].GetDisplayName(pctx, null, out filepathname);
// Clean up
Marshal.ReleaseComObject(pctx);
// Search for the workbook
if (filepathname.IndexOf(xlfile) != -1)
{
object roval;
// Get a handle on the workbook
prot.GetObject(monikers[0], out roval);
return roval as Workbook;
}
}
} catch {
return null;
} finally {
// Clean up
if(prot!=null) Marshal.ReleaseComObject(prot);
if(pmonkenum!=null) Marshal.ReleaseComObject(pmonkenum);
}
return null;
}
The code works fine and I am able to work with the Excel workbook UNTIL no other workbook is open in the system(another workbook opened by double clicking the file in the local system).
The following is the scenario:
1) I opened a workbook from explorer by double clicking it. Let's call it Excel A. This started an EXCEL.EXE process.
2) I navigated to another Excel workbook from my Windows Form web browser. Let's call it Excel B. Excel B opens in the Form.It uses the already existing EXCEL.EXE started in step 1.
3) Now if I try to edit Excel A(opened in step 1).It does not allow me. The focus is always there on Excel B(navigated in step 2). I cannot edit the cells, select text or even close Excel A.
Please kindly let me know any solution to solve it.
This issue was solved with the hard work of the team and with the help from Microsoft deep support. I share the final solution in Google open source projects https://code.google.com/p/form-based-excel-solution/

How to detect whether an Excel workbook is closed (using Interop in C#)?

I'm working on a C# project that uses Microsoft.Office.Interop.Excel.Application to read values from an Excel file:
try {
Application xlApp = new Application();
Workbook workbook = xlApp.Workbooks.Open(filename)
// ... Load Excel values ...
} finally {
// ... Tidy up ...
}
In the finally block I'd like to make sure everything is closed and disposed of properly so nothing hangs around in memory and Excel closes cleanly. Have seen various threads about what this code should look like (more complex than I thought!) but one thing it might include is:
if (workbook != null) {
workbook.Close();
// ... possibly also Marshal.FinalReleaseComObject(workbook);
}
However, this throws an error if the workbook is already closed so how can I safely check this? Would prefer not to just catch the error if possible as this type of thing tends to distort debugging. Is there a clean way of finding out the workbook state before closing?
One more q - am wondering if workbook.Close() is needed if xlApp.Quit(); is done afterwards - would quitting the Excel application cause workbook.Close() (and any COM object releasing) to happen implicitly?
As you are opening the workbook the best advice is to keep track of the workbook and close it when appropriate. If your code is quite detailed then you could store a Boolean value indicating whether the file is currently open or closed.
In Excel there is no property such as IsOpen. You could try to reference the workbook:
Workbook wbTest = xlApp.Workbooks.get_Item("some.xlsx");
but this creates a COM error if the book is not open, so gets quite messy.
Instead, create your own function IsOpen that returns a boolean and loops through the currently open workbooks (the Workbooks collection) checking the name, using code like this:
foreach (_Workbook wb in xlApp.Workbooks)
{
Console.WriteLine(wb.Name);
}
workbook.Close() would not be necessary if the workbook has been saved - reflecting the normal behaviour of Excel. However, all Excel object references need to be released. As you have discovered, this is a little fiddly, and Close and Quit do not achieve this on their own.
static bool IsOpen(string book)
{
foreach (_Workbook wb in xlApp.Workbooks)
{
if (wb.Name.Contains(book))
{
return true;
}
}
return false;
}

Load Excel process and close the entire process when closing a document

In my C# application, I can Open a new Excel process by clicking a button.
Then, it waits for input idle and load a new Excel file. I have to accomplish it because I want all macros and tools to be loaded before loading my document (otherwise, there is a rendering bug).
The Process Id is saved in a var, and when I click again on that button, with the PID, if the process already exists, the Process has the focus, otherwise, a new process is created :
Process processExcel = null;
if (pId_m.HasValue)
{
try
{
processExcel = Process.GetProcessById(pId_m.Value);
}
catch (Exception ex)
{
Log.MonitoringLogger.Error("Unable to find Pid : " + pId_m,ex);
}
}
if (processExcel != null && (processExcel.HasExited == false))
{
AutomationElement element = AutomationElement.FromHandle(processExcel.MainWindowHandle);
if (element != null)
{
element.SetFocus();
}
}
else
{
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = "Excel.exe";
info.Arguments = " /e ";
processExcel_l = Process.Start(info);
pId_m = processExcel_l.Id;
processExcel_l.WaitForInputIdle();
processExcel_l.StartInfo = new ProcessStartInfo(path);
processExcel_l.Start();
}
The problem is the following : when I close the Excel document window (and not Excel window), and I click the button, the focus is set to the Excel process, but without any document...
This behavior is logic, but not working for what I want...
I have seen a software that load a new process and a new document inside, but when clicking on the document close button, the entire process was exited...
How to reproduce the same?
Edit : Ok,
Finally instead of setting the focus on the process, I launched a file on this process (which set focus if the file is already open).
It's not what I really wanted to do, but it solve my problem...
I would suggest utilizing the Excel COM model rather than running the process by hand. You can subscribe to events of the Worksheet and close the application.
These MSDN documents might be helpful:
http://msdn.microsoft.com/en-us/library/wss56bz7(v=vs.80).aspx
http://msdn.microsoft.com/en-us/library/microsoft.office.interop.excel.worksheet_members.aspx
Wait for Excel process ending and then close your app:
processExcel_l.WaitForExit();
Application.Exit();
To handle closing only Excel document (not Excel process) you probably need to reference to Excel API.

Killing all processes generated by a form in C#

I would like to build a list of all processes generated by events in a form and kill them all when the form closes. How do I code this?
When I worked with the Excel Interop we had several processes created by it, and we had problems if we had real Excel workbooks opened at the same time the program was running.
If you kill the process and your user has a workbook opened, you might close their Excel instead. I would suggest getting a list of the PIDs for the processes that have Excel in their name before opening the workbook in your program, then get the list again immediately after opening it, and by seeing what is new determine the PID of your process. When you're done with the workbook, kill the process with the PID you retrieved.
Edit: when you open the workbook in code, a new process is created. Before opening the workbook, you have a list of processes - let's say 10 (by usingpart of the code Harendra wrote). Then you open the workbook, and get the list again - and you'll have 11 processes (you might have more, but only one of those is Excel). By comparing the two lists, you get the ID of the new process, which is your opened workbook. You add to a list or processes opened by you, and when closing the program, kill all the processes from the list.
Try this...
Process[] procList = Process.GetProcesses();
for (int i = 0; i <= procList.Length - 1; i ++) {
string strProcName = procList[i].ProcessName;
string strProcTitle = procList[i].MainWindowTitle();
//check for your process name.. here i m checking excel process
if (strProcName.ToLower().Trim().Contains("excel")) {
procList[i].Kill();
}
}
In continuation with reply from Harendra, you can store all the process ID of process invoked by you ! and close them at the time of closing.
This statement is valid only if you are using Process.Start to start processes

Categories