workbook objeto application excel vba excel-vba

objeto - ¿Puede VBA alcanzar a través de instancias de Excel?



objeto application excel vba (7)

Creo que VBA es más poderoso de lo que cree Charles;)

Si solo hay alguna forma difícil de señalar la instancia específica de GetObject y CreateObject , ¡se resolverá tu problema!

EDITAR:

Si usted es el creador de todas las instancias, no debería haber problemas con cosas como la lista de libros de trabajo. Echa un vistazo a este código:

Sub Excels() Dim currentExcel As Excel.Application Dim newExcel As Excel.Application Set currentExcel = GetObject(, "excel.application") Set newExcel = CreateObject("excel.application") newExcel.Visible = True newExcel.Workbooks.Add ''and so on... End Sub

¿Puede una macro de Excel VBA, que se ejecuta en una instancia de Excel, acceder a los libros de otra instancia en ejecución de Excel? Por ejemplo, me gustaría crear una lista de todos los libros abiertos en cualquier instancia de ejecución de Excel.


Gracias a esta excelente publicación tuve una rutina para encontrar una matriz de todas las aplicaciones de Excel que se ejecutan actualmente en la máquina. El problema es que acabo de actualizar a Office 2013 64 bit y todo salió mal.

Existe el típico faff de convertir ... Declare Function ... en ... Declare PtrSafe Function ... , que está bien documentada en otra parte. Sin embargo, en lo que no pude encontrar ninguna documentación es en el hecho de que la jerarquía de ventanas (''XLMAIN'' -> ''XLDESK'' -> ''EXCEL7'') que el código original espera ha cambiado después de esta actualización. Para cualquiera que siga mis pasos, para ahorrarte una tarde de búsqueda, pensé en publicar mi script actualizado. Es difícil de probar, pero creo que también debería ser compatible con versiones anteriores.

Option Explicit #If Win64 Then Private Declare PtrSafe Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As LongPtr, ByVal hWnd2 As LongPtr, ByVal lpsz1 As String, ByVal lpsz2 As String) As LongPtr Private Declare PtrSafe Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal Hwnd As LongPtr, ByVal lpClassName As String, ByVal nMaxCount As LongPtr) As LongPtr Private Declare PtrSafe Function IIDFromString Lib "ole32" (ByVal lpsz As LongPtr, ByRef lpiid As UUID) As LongPtr Private Declare PtrSafe Function AccessibleObjectFromWindow Lib "oleacc" (ByVal Hwnd As LongPtr, ByVal dwId As LongPtr, ByRef riid As UUID, ByRef ppvObject As Object) As LongPtr #Else Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long Private Declare Function IIDFromString Lib "ole32" (ByVal lpsz As Long, ByRef lpiid As UUID) As Long Private Declare Function AccessibleObjectFromWindow Lib "oleacc" (ByVal hwnd As Long, ByVal dwId As Long, ByRef riid As UUID, ByRef ppvObject As Object) As Long #End If Type UUID ''GUID Data1 As Long Data2 As Integer Data3 As Integer Data4(7) As Byte End Type Const IID_IDispatch As String = "{00020400-0000-0000-C000-000000000046}" Const OBJID_NATIVEOM As LongPtr = &HFFFFFFF0 '' Run as entry point of example Public Sub Test() Dim i As Long Dim xlApps() As Application If GetAllExcelInstances(xlApps) Then For i = LBound(xlApps) To UBound(xlApps) If xlApps(i).Workbooks(1).Name <> ThisWorkbook.Name Then MsgBox (xlApps(i).Workbooks(1).Name) End If Next End If End Sub '' Actual public facing function to be called in other code Public Function GetAllExcelInstances(xlApps() As Application) As Long On Error GoTo MyErrorHandler Dim n As Long #If Win64 Then Dim hWndMain As LongPtr #Else Dim hWndMain As Long #End If Dim app As Application '' Cater for 100 potential Excel instances, clearly could be better ReDim xlApps(1 To 100) hWndMain = FindWindowEx(0&, 0&, "XLMAIN", vbNullString) Do While hWndMain <> 0 Set app = GetExcelObjectFromHwnd(hWndMain) If Not (app Is Nothing) Then If n = 0 Then n = n + 1 Set xlApps(n) = app ElseIf checkHwnds(xlApps, app.Hwnd) Then n = n + 1 Set xlApps(n) = app End If End If hWndMain = FindWindowEx(0&, hWndMain, "XLMAIN", vbNullString) Loop If n Then ReDim Preserve xlApps(1 To n) GetAllExcelInstances = n Else Erase xlApps End If Exit Function MyErrorHandler: MsgBox "GetAllExcelInstances" & vbCrLf & vbCrLf & "Err = " & Err.Number & vbCrLf & "Description: " & Err.Description End Function #If Win64 Then Private Function checkHwnds(xlApps() As Application, Hwnd As LongPtr) As Boolean #Else Private Function checkHwnds(xlApps() As Application, Hwnd As Long) As Boolean #End If Dim i As Integer For i = LBound(xlApps) To UBound(xlApps) If xlApps(i).Hwnd = Hwnd Then checkHwnds = False Exit Function End If Next i checkHwnds = True End Function #If Win64 Then Private Function GetExcelObjectFromHwnd(ByVal hWndMain As LongPtr) As Application #Else Private Function GetExcelObjectFromHwnd(ByVal hWndMain As Long) As Application #End If On Error GoTo MyErrorHandler #If Win64 Then Dim hWndDesk As LongPtr Dim Hwnd As LongPtr #Else Dim hWndDesk As Long Dim Hwnd As Long #End If Dim strText As String Dim lngRet As Long Dim iid As UUID Dim obj As Object hWndDesk = FindWindowEx(hWndMain, 0&, "XLDESK", vbNullString) If hWndDesk <> 0 Then Hwnd = FindWindowEx(hWndDesk, 0, vbNullString, vbNullString) Do While Hwnd <> 0 strText = String$(100, Chr$(0)) lngRet = CLng(GetClassName(Hwnd, strText, 100)) If Left$(strText, lngRet) = "EXCEL7" Then Call IIDFromString(StrPtr(IID_IDispatch), iid) If AccessibleObjectFromWindow(Hwnd, OBJID_NATIVEOM, iid, obj) = 0 Then ''S_OK Set GetExcelObjectFromHwnd = obj.Application Exit Function End If End If Hwnd = FindWindowEx(hWndDesk, Hwnd, vbNullString, vbNullString) Loop On Error Resume Next End If Exit Function MyErrorHandler: MsgBox "GetExcelObjectFromHwnd" & vbCrLf & vbCrLf & "Err = " & Err.Number & vbCrLf & "Description: " & Err.Description End Function


La respuesta de Cornelio es parcialmente correcta. Su código obtiene la instancia actual y luego crea una nueva instancia. GetObject solo obtiene la primera instancia, sin importar cuántas instancias estén disponibles. La pregunta que creo es cómo se puede obtener una instancia específica de entre muchas instancias.

Para un proyecto de VBA, cree dos módulos, un módulo de código y el otro como formulario con un botón de comando llamado Command1. Es posible que deba agregar una referencia a Microsoft.Excel.

Este código muestra todo el nombre de cada libro de trabajo para cada instancia de ejecución de Excel en la ventana Inmediato.

''------------- Code Module -------------- Option Explicit Declare Function FindWindowEx Lib "User32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long Declare Function GetClassName Lib "User32" Alias "GetClassNameA" (ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long Declare Function IIDFromString Lib "ole32" (ByVal lpsz As Long, ByRef lpiid As UUID) As Long Declare Function AccessibleObjectFromWindow Lib "oleacc" (ByVal hWnd As Long, ByVal dwId As Long, ByRef riid As UUID, ByRef ppvObject As Object) As Long Type UUID ''GUID Data1 As Long Data2 As Integer Data3 As Integer Data4(7) As Byte End Type ''------------- Form Module -------------- Option Explicit Const IID_IDispatch As String = "{00020400-0000-0000-C000-000000000046}" Const OBJID_NATIVEOM As Long = &HFFFFFFF0 ''Sub GetAllWorkbookWindowNames() Sub Command1_Click() On Error GoTo MyErrorHandler Dim hWndMain As Long hWndMain = FindWindowEx(0&, 0&, "XLMAIN", vbNullString) Do While hWndMain <> 0 GetWbkWindows hWndMain hWndMain = FindWindowEx(0&, hWndMain, "XLMAIN", vbNullString) Loop Exit Sub MyErrorHandler: MsgBox "GetAllWorkbookWindowNames" & vbCrLf & vbCrLf & "Err = " & Err.Number & vbCrLf & "Description: " & Err.Description End Sub Private Sub GetWbkWindows(ByVal hWndMain As Long) On Error GoTo MyErrorHandler Dim hWndDesk As Long hWndDesk = FindWindowEx(hWndMain, 0&, "XLDESK", vbNullString) If hWndDesk <> 0 Then Dim hWnd As Long hWnd = FindWindowEx(hWndDesk, 0, vbNullString, vbNullString) Dim strText As String Dim lngRet As Long Do While hWnd <> 0 strText = String$(100, Chr$(0)) lngRet = GetClassName(hWnd, strText, 100) If Left$(strText, lngRet) = "EXCEL7" Then GetExcelObjectFromHwnd hWnd Exit Sub End If hWnd = FindWindowEx(hWndDesk, hWnd, vbNullString, vbNullString) Loop On Error Resume Next End If Exit Sub MyErrorHandler: MsgBox "GetWbkWindows" & vbCrLf & vbCrLf & "Err = " & Err.Number & vbCrLf & "Description: " & Err.Description End Sub Public Function GetExcelObjectFromHwnd(ByVal hWnd As Long) As Boolean On Error GoTo MyErrorHandler Dim fOk As Boolean fOk = False Dim iid As UUID Call IIDFromString(StrPtr(IID_IDispatch), iid) Dim obj As Object If AccessibleObjectFromWindow(hWnd, OBJID_NATIVEOM, iid, obj) = 0 Then ''S_OK Dim objApp As Excel.Application Set objApp = obj.Application Debug.Print objApp.Workbooks(1).Name Dim myWorksheet As Worksheet For Each myWorksheet In objApp.Workbooks(1).Worksheets Debug.Print " " & myWorksheet.Name DoEvents Next fOk = True End If GetExcelObjectFromHwnd = fOk Exit Function MyErrorHandler: MsgBox "GetExcelObjectFromHwnd" & vbCrLf & vbCrLf & "Err = " & Err.Number & vbCrLf & "Description: " & Err.Description End Function


No creo que esto sea posible utilizando solo VBA porque el objeto de nivel más alto que puede obtener es el objeto Aplicación, que es la instancia actual de Excel.


Solo para agregar a la respuesta de James MacAdie, creo que hagas el redim demasiado tarde porque en la función checkHwnds terminas con un error fuera de rango ya que estás tratando de verificar valores de hasta 100 aunque aún no hayas poblado el matriz completamente? Modifiqué el código a continuación y ahora está trabajando para mí.

'' Actual public facing function to be called in other code Public Function GetAllExcelInstances(xlApps() As Application) As Long On Error GoTo MyErrorHandler Dim n As Long #If Win64 Then Dim hWndMain As LongPtr #Else Dim hWndMain As Long #End If Dim app As Application '' Cater for 100 potential Excel instances, clearly could be better ReDim xlApps(1 To 100) hWndMain = FindWindowEx(0&, 0&, "XLMAIN", vbNullString) Do While hWndMain <> 0 Set app = GetExcelObjectFromHwnd(hWndMain) If Not (app Is Nothing) Then If n = 0 Then n = n + 1 ReDim Preserve xlApps(1 To n) Set xlApps(n) = app ElseIf checkHwnds(xlApps, app.Hwnd) Then n = n + 1 ReDim Preserve xlApps(1 To n) Set xlApps(n) = app End If End If hWndMain = FindWindowEx(0&, hWndMain, "XLMAIN", vbNullString) Loop If n Then GetAllExcelInstances = n Else Erase xlApps End If Exit Function MyErrorHandler: MsgBox "GetAllExcelInstances" & vbCrLf & vbCrLf & "Err = " & Err.Number & vbCrLf & "Description: " & Err.Description End Function


Tuve un problema / objetivo similar.

Y conseguí que ForEachLoops respondiera funcionando, pero hay un cambio que se necesita. En la función inferior (GetExcelObjectFromHwnd), utilizó el índice de libro de trabajo de 1 en ambos comandos debug.print. El resultado es que solo ves el primer WB.

Así que tomé su código, y puse un bucle for dentro de GetExcelObjectFromHwnd, y cambié el 1 a un contador. el resultado es que puedo obtener TODOS los libros de Excel activos y devolver la información que necesito para acceder a las instancias de Excel y acceder a otras WB.

Y creé un Type para simplificar la recuperación de la información y devolverla a la subrutina llamante:

Type TargetWBType name As String returnObj As Object returnApp As Excel.Application returnWBIndex As Integer End Type

Para el nombre, simplemente usé el nombre de archivo base, por ejemplo, "example.xls". Este fragmento prueba la funcionalidad al escupir el valor de A6 en cada WS del objetivo WB. Al igual que:

Dim targetWB As TargetWBType targetWB.name = "example.xls" Call GetAllWorkbookWindowNames(targetWB) If Not targetWB.returnObj Is Nothing Then Set targetWB.returnApp = targetWB.returnObj.Application Dim ws As Worksheet For Each ws In targetWB.returnApp.Workbooks(targetWB.returnWBIndex).Worksheets MsgBox ws.Range("A6").Value Next Else MsgBox "Target WB Not found" End If

Así que ahora el módulo COMPLETO que ForEachLoop originalmente hizo se ve así, y he indicado los cambios que hice. Tiene una ventana emergente de msgbox, que dejé en el fragmento para fines de depuración. Elimínalo una vez que encuentre tu objetivo. El código:

Declare Function FindWindowEx Lib "User32" Alias "FindWindowExA" (ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long Declare Function GetClassName Lib "User32" Alias "GetClassNameA" (ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long Declare Function IIDFromString Lib "ole32" (ByVal lpsz As Long, ByRef lpiid As UUID) As Long Declare Function AccessibleObjectFromWindow Lib "oleacc" (ByVal hWnd As Long, ByVal dwId As Long, ByRef riid As UUID, ByRef ppvObject As Object) As Long Type UUID ''GUID Data1 As Long Data2 As Integer Data3 As Integer Data4(7) As Byte End Type ''------------- Form Module -------------- Option Explicit Const IID_IDispatch As String = "{00020400-0000-0000-C000-000000000046}" Const OBJID_NATIVEOM As Long = &HFFFFFFF0 ''My code: added targetWB Sub GetAllWorkbookWindowNames(targetWB As TargetWBType) On Error GoTo MyErrorHandler Dim hWndMain As Long hWndMain = FindWindowEx(0&, 0&, "XLMAIN", vbNullString) Do While hWndMain <> 0 GetWbkWindows hWndMain, targetWB ''My code: added targetWB hWndMain = FindWindowEx(0&, hWndMain, "XLMAIN", vbNullString) Loop Exit Sub MyErrorHandler: MsgBox "GetAllWorkbookWindowNames" & vbCrLf & vbCrLf & "Err = " & Err.Number & vbCrLf & "Description: " & Err.Description End Sub ''My code: added targetWB Private Sub GetWbkWindows(ByVal hWndMain As Long, targetWB As TargetWBType) On Error GoTo MyErrorHandler Dim hWndDesk As Long hWndDesk = FindWindowEx(hWndMain, 0&, "XLDESK", vbNullString) If hWndDesk <> 0 Then Dim hWnd As Long hWnd = FindWindowEx(hWndDesk, 0, vbNullString, vbNullString) Dim strText As String Dim lngRet As Long Do While hWnd <> 0 strText = String$(100, Chr$(0)) lngRet = GetClassName(hWnd, strText, 100) If Left$(strText, lngRet) = "EXCEL7" Then GetExcelObjectFromHwnd hWnd, targetWB ''My code: added targetWB Exit Sub End If hWnd = FindWindowEx(hWndDesk, hWnd, vbNullString, vbNullString) Loop On Error Resume Next End If Exit Sub MyErrorHandler: MsgBox "GetWbkWindows" & vbCrLf & vbCrLf & "Err = " & Err.Number & vbCrLf & "Description: " & Err.Description End Sub ''My code: added targetWB Public Function GetExcelObjectFromHwnd(ByVal hWnd As Long, targetWB As TargetWBType) As Boolean On Error GoTo MyErrorHandler Dim fOk As Boolean fOk = False Dim iid As UUID Call IIDFromString(StrPtr(IID_IDispatch), iid) Dim obj As Object If AccessibleObjectFromWindow(hWnd, OBJID_NATIVEOM, iid, obj) = 0 Then ''S_OK Dim objApp As Excel.Application Set objApp = obj.Application ''My code Dim wbCount As Integer For wbCount = 1 To objApp.Workbooks.Count ''End my code ''Not my code Debug.Print objApp.Workbooks(wbCount).name ''My code If LCase(objApp.Workbooks(wbCount).name) = LCase(targetWB.name) Then MsgBox ("Found target: " & targetWB.name) Set targetWB.returnObj = obj targetWB.returnWBIndex = wbCount End If ''End My code ''Not my code Dim myWorksheet As Worksheet For Each myWorksheet In objApp.Workbooks(wbCount).Worksheets Debug.Print " " & myWorksheet.name DoEvents Next ''My code Next ''Not my code fOk = True End If GetExcelObjectFromHwnd = fOk Exit Function MyErrorHandler: MsgBox "GetExcelObjectFromHwnd" & vbCrLf & vbCrLf & "Err = " & Err.Number & vbCrLf & "Description: " & Err.Description End Function

Repito, esto funciona, y usando las variables dentro del tipo TargetWB, estoy accediendo de manera confiable a los libros de trabajo y hojas de trabajo en las instancias de Excel.

El único problema potencial que veo con mi solución es si tienes múltiples WB con el mismo nombre. En este momento, creo que devolvería la última instancia de ese nombre. Si agregamos una Salida para en el If Then, creo que devolverá la primera instancia de la misma. No probé esta parte por completo, ya que en mi aplicación solo hay una instancia del archivo abierta.


Creo que dentro de VBA puede obtener acceso al objeto de la aplicación en otra instancia en ejecución . Si conoce el nombre de un libro abierto en la otra instancia, puede obtener una referencia al objeto de la aplicación. Ver la página de Allen Waytt

La ultima parte,

Dim xlApp As Excel.Application
Set xlApp = GetObject("c:/mypath/ExampleBook.xlsx").Application

Me permitió obtener un puntero al objeto de aplicación de la instancia que tenía ExampleBook.xlsx abierto.

Creo que "ExampleBook" debe ser la ruta completa, al menos en Excel 2010. Actualmente estoy experimentando con esto, así que intentaré actualizarlo a medida que obtenga más detalles.

Presumiblemente, puede haber complicaciones si instancias separadas tienen el mismo libro abierto, pero solo uno puede tener acceso de escritura.