This is in regards to the selected answer for this question:
How to iterate through instance of Excel c#
This code works great for getting all instances of Excel unless one of these instances has the VBA Editor open. The code breaks when it tries to find the class for EXCEL7
. This is the class for the workbook subwindow. While debugging I determined that when enumerating the child windows, the EXCEL7 child window can not be located. Classes like VbaWindow
instead show up. I even tried to get the Excel.Window with the window handle for this vba window class but it failed. How can I still get the method AccessibleObjectFromWindow
to reference the Excel.Window, which I can then use to reference the application. Here is my modified method (I already have the Excel Process ID... all other declarations were omitted for readability):
internal static Excel.Application GetExcelInstance(int procID)
{
EnumChildCallback cb;
Process p = Process.GetProcessById(procID);
if (p != null)
{
if ((int)p.MainWindowHandle > 0)
{
int childWindow = 0;
cb = new EnumChildCallback(EnumChildProc);
EnumChildWindows((int)p.MainWindowHandle, cb, ref childWindow);
if (childWindow > 0)
{
const uint OBJID_NATIVEOM = 0xFFFFFFF0;
// GUIDs used by the OLE Automation Protocol:
// https://msdn.microsoft.com/en-us/library/cc237842.aspx
Guid IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
Excel.Window window = null;
int res = AccessibleObjectFromWindow
(
childWindow,
OBJID_NATIVEOM,
IID_IDispatch.ToByteArray(),
ref window
);
if (res >= 0)
{
return window.Application;
}
}
}
}
return null;
}
// If VBA is open this method will fail when enumerating
// all child windows of the excel process
// EXCEL7 will not be found in child windows but other windows
// will be found like the window for class "VbaWindow"
private static bool EnumChildProc(int hwndChild, ref int lParam)
{
StringBuilder buf = new StringBuilder(128);
GetClassName(hwndChild, buf, 128);
// More info on excel classes:
// http://www.mrexcel.com/forum/excel-questions/54007-worksheet-class-findwindow-api.html
if (buf.ToString() == "EXCEL7")
{
lParam = hwndChild;
return false;
}
return true;
}
I had exactly the same problem. I solved it by:
Here's some example code:
public class ExcelInstances
{
HashSet<int> _ExcelProcessIDs;
List<int> _ExcelTopLevelWindowHwnds;
List<Excel.Application> _XLInstances;
public Excel.Application[] GetExcelInstances()
{
_XLInstances = new List<Excel.Application>();
_ExcelProcessIDs = new HashSet<int>();
_ExcelTopLevelWindowHwnds = new List<int>();
foreach (Process p in Process.GetProcessesByName("EXCEL")) _ExcelProcessIDs.Add(p.Id); //find all process ids related to Excel
int hwnd = 0;
var cb = new WinAPI.WindowEnumProc(GetAllExcelTopLevelWindowHwnds);
WinAPI.EnumWindows(cb, ref hwnd);
foreach (var hwnd2 in _ExcelTopLevelWindowHwnds)
{
var excelHwnd = 0;
var cb2 = new WinAPI.WindowEnumProc(GetExcelWorkbooksFromExcelWindowHandles);
WinAPI.EnumChildWindows(hwnd2, cb2, ref excelHwnd);
}
return _XLInstances.ToArray();
}
private bool GetAllExcelTopLevelWindowHwnds(int hwnd, ref int lParam)
{
int id = 0;
WinAPI.GetWindowThreadProcessId(hwnd, ref id);
if (_ExcelProcessIDs.Contains(id))
{
if (hwnd > 0)
{
_ExcelTopLevelWindowHwnds.Add(hwnd);
}
}
return true;
}
private bool GetExcelWorkbooksFromExcelWindowHandles(int hwndChild, ref int lParam)
{
int id = 0;
WinAPI.GetWindowThreadProcessId(hwndChild, ref id);
StringBuilder buf = new StringBuilder(128);
WinAPI.GetClassName(hwndChild, buf, 128);
string clsName = buf.ToString();
if (clsName == "EXCEL7")
{
lParam = hwndChild;
var wb = UsefulStaticMethods.GetActiveWorkbookFromExcelHandle(hwndChild);
if (wb != null) _XLInstances.Add(wb.Parent);
}
return true;
}
}