Using Process.Start on Excel
-
I am trying to use Process.Start to launch Excel. If I immediately grab the process returned by the Start method, any addins or extensions to Excel (xlls or xlam) will not work. If I wait until Excel finishes opening and loading all the addins, then grab the process, it works. Need them to work for this project. I mashed up a class from code I found around to either grab a reference to an existing instance of Excel or to start a new one if it isn't running. Unfortunately, it doesn't work. If the Process.Start method is called, Excel starts loading. The WaitForInputIdle method waits for the message loop to start. But when the message loop starts, Excel is still loading things and has not created any child windows. So the class tries to enumerate the child windows but there are none. And I discovered that once you call EnumChildWindows() it "freezes" things in that it will not refer to a child window created subsequently even if you call it again after the child window is created. So loops to wait are out. One solution is to recursively create new instances of the class to get around the EnumChildWindows problem, but I end up with 1000's of these instances. As a kludge, I have a long Thread.Sleep in the code to wait until everything is open. It works correctly but I would like to know if there is a better way to determine if a process has finished opening, especially given the bug/feature in EnumChildWindows.
public class ExcelInteropService { private const string EXCEL\_CLASS\_NAME = "EXCEL7"; private const uint DW\_OBJECTID = 0xFFFFFFF0; private static Guid rrid = new Guid("{00020400-0000-0000-C000-000000000046}"); public delegate bool EnumChildCallback(int hwnd, ref int lParam); \[DllImport("Oleacc.dll")\] public static extern int AccessibleObjectFromWindow(int hwnd, uint dwObjectID, byte\[\] riid, ref Microsoft.Office.Interop.Excel.Window ptr); \[DllImport("User32.dll")\] public static extern bool EnumChildWindows(int hWndParent, EnumChildCallback lpEnumFunc, ref int lParam); \[DllImport("User32.dll")\] public static extern int GetClassName(int hWnd, StringBuilder lpClassName, int nMaxCount); public static Microsoft.Office.Interop.Excel.Application GetExcelInterop(int? processId = null) { var p = processId.HasValue ? Process.GetProcessById(processId.Value) : Process.Start("excel.exe"); p.WaitForInputIdle(); System.Threading.Thread.Sleep(60000); Debug.Assert(p != null, "p != null"); try { return new ExcelInte
-
I am trying to use Process.Start to launch Excel. If I immediately grab the process returned by the Start method, any addins or extensions to Excel (xlls or xlam) will not work. If I wait until Excel finishes opening and loading all the addins, then grab the process, it works. Need them to work for this project. I mashed up a class from code I found around to either grab a reference to an existing instance of Excel or to start a new one if it isn't running. Unfortunately, it doesn't work. If the Process.Start method is called, Excel starts loading. The WaitForInputIdle method waits for the message loop to start. But when the message loop starts, Excel is still loading things and has not created any child windows. So the class tries to enumerate the child windows but there are none. And I discovered that once you call EnumChildWindows() it "freezes" things in that it will not refer to a child window created subsequently even if you call it again after the child window is created. So loops to wait are out. One solution is to recursively create new instances of the class to get around the EnumChildWindows problem, but I end up with 1000's of these instances. As a kludge, I have a long Thread.Sleep in the code to wait until everything is open. It works correctly but I would like to know if there is a better way to determine if a process has finished opening, especially given the bug/feature in EnumChildWindows.
public class ExcelInteropService { private const string EXCEL\_CLASS\_NAME = "EXCEL7"; private const uint DW\_OBJECTID = 0xFFFFFFF0; private static Guid rrid = new Guid("{00020400-0000-0000-C000-000000000046}"); public delegate bool EnumChildCallback(int hwnd, ref int lParam); \[DllImport("Oleacc.dll")\] public static extern int AccessibleObjectFromWindow(int hwnd, uint dwObjectID, byte\[\] riid, ref Microsoft.Office.Interop.Excel.Window ptr); \[DllImport("User32.dll")\] public static extern bool EnumChildWindows(int hWndParent, EnumChildCallback lpEnumFunc, ref int lParam); \[DllImport("User32.dll")\] public static extern int GetClassName(int hWnd, StringBuilder lpClassName, int nMaxCount); public static Microsoft.Office.Interop.Excel.Application GetExcelInterop(int? processId = null) { var p = processId.HasValue ? Process.GetProcessById(processId.Value) : Process.Start("excel.exe"); p.WaitForInputIdle(); System.Threading.Thread.Sleep(60000); Debug.Assert(p != null, "p != null"); try { return new ExcelInte
mjackson11 wrote:
grab a reference to an existing instance of Excel or to start a new one if it isn't running
I haven't done this for a while, but I think you can replace all of that code with:
using System;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;public static class ExcelInteropService
{
public static Excel.Application FindOrStartExcel()
{
try
{
return (Excel.Application)Marshal.GetActiveObject("Excel.Application");
}
catch (COMException)
{
}// Excel is not in the ROT; start a new instance: return new Excel.Application(); }
}
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
-
mjackson11 wrote:
grab a reference to an existing instance of Excel or to start a new one if it isn't running
I haven't done this for a while, but I think you can replace all of that code with:
using System;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;public static class ExcelInteropService
{
public static Excel.Application FindOrStartExcel()
{
try
{
return (Excel.Application)Marshal.GetActiveObject("Excel.Application");
}
catch (COMException)
{
}// Excel is not in the ROT; start a new instance: return new Excel.Application(); }
}
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
Unfortunately, the new Excel.Application call is buggy. The Excel instance that is launched will show the addins as being loaded but the addins cannot be used from a vba macro.
-
I am trying to use Process.Start to launch Excel. If I immediately grab the process returned by the Start method, any addins or extensions to Excel (xlls or xlam) will not work. If I wait until Excel finishes opening and loading all the addins, then grab the process, it works. Need them to work for this project. I mashed up a class from code I found around to either grab a reference to an existing instance of Excel or to start a new one if it isn't running. Unfortunately, it doesn't work. If the Process.Start method is called, Excel starts loading. The WaitForInputIdle method waits for the message loop to start. But when the message loop starts, Excel is still loading things and has not created any child windows. So the class tries to enumerate the child windows but there are none. And I discovered that once you call EnumChildWindows() it "freezes" things in that it will not refer to a child window created subsequently even if you call it again after the child window is created. So loops to wait are out. One solution is to recursively create new instances of the class to get around the EnumChildWindows problem, but I end up with 1000's of these instances. As a kludge, I have a long Thread.Sleep in the code to wait until everything is open. It works correctly but I would like to know if there is a better way to determine if a process has finished opening, especially given the bug/feature in EnumChildWindows.
public class ExcelInteropService { private const string EXCEL\_CLASS\_NAME = "EXCEL7"; private const uint DW\_OBJECTID = 0xFFFFFFF0; private static Guid rrid = new Guid("{00020400-0000-0000-C000-000000000046}"); public delegate bool EnumChildCallback(int hwnd, ref int lParam); \[DllImport("Oleacc.dll")\] public static extern int AccessibleObjectFromWindow(int hwnd, uint dwObjectID, byte\[\] riid, ref Microsoft.Office.Interop.Excel.Window ptr); \[DllImport("User32.dll")\] public static extern bool EnumChildWindows(int hWndParent, EnumChildCallback lpEnumFunc, ref int lParam); \[DllImport("User32.dll")\] public static extern int GetClassName(int hWnd, StringBuilder lpClassName, int nMaxCount); public static Microsoft.Office.Interop.Excel.Application GetExcelInterop(int? processId = null) { var p = processId.HasValue ? Process.GetProcessById(processId.Value) : Process.Start("excel.exe"); p.WaitForInputIdle(); System.Threading.Thread.Sleep(60000); Debug.Assert(p != null, "p != null"); try { return new ExcelInte
What do you actually want to achieve? To me, your approach looks line an "XY problem": you do not describe the problem proper, but your idea of a solution which failed somewhere, and now you ask us how to procede on your wrong way. Let's get a few steps back and find the correct way to cope with it.
-
Unfortunately, the new Excel.Application call is buggy. The Excel instance that is launched will show the addins as being loaded but the addins cannot be used from a vba macro.
According to this SO question[^], you need to loop through the installed add-ins, set the
Installed
property tofalse
, and then back totrue
. Alternatively, this blog post[^] suggests that you should use theApplication.AddIns.Add
method to load the add-ins, and explains why they're not loaded when you usenew Excel.Application()
.
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
-
According to this SO question[^], you need to loop through the installed add-ins, set the
Installed
property tofalse
, and then back totrue
. Alternatively, this blog post[^] suggests that you should use theApplication.AddIns.Add
method to load the add-ins, and explains why they're not loaded when you usenew Excel.Application()
.
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
OMG, I spent two days on Google looking for this. Thank you thank you thank you. It was driving me nuts.
Mark Jackson