即使VBA由Excel实例打开,也要附加到现有Excel实例

时间:2015-05-08 15:47:52

标签: c# excel vba interop marshalling

这与此问题的选定答案有关:
How to iterate through instance of Excel c#

此代码非常适合获取Excel的所有实例,除非其中一个实例打开了VBA编辑器。代码在尝试查找EXCEL7的类时会中断。这是工作簿子窗口的类。在调试时我确定在枚举子窗口时,无法找到EXCEL7子窗口。像VbaWindow这样的类会显示出来。我甚至试图用这个vba窗口类的窗口句柄获取Excel.Window,但它失败了。我怎样才能让方法AccessibleObjectFromWindow引用Excel.Window,然后我可以使用它来引用应用程序。这是我修改过的方法(我已经有了Excel Process ID ...为了便于阅读,省略了所有其他声明):

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;
}

1 个答案:

答案 0 :(得分:1)

我有完全相同的问题。我解决了它:

  1. 获取所有Excel流程ID的列表
  2. 循环所有顶级窗口并检查其进程ID是否在Excel ID列表中。如果是,则将其添加到另一个列表中。 (使用EnumWindows和GetWindowThreadProcessID)
  3. 然后遍历这个Excel hwnds列表,基本上做你以前做过的事情,即搜索他们的子窗口找到一个类名为EXCEL7的文件
  4. 以下是一些示例代码:

    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;
        }
    }