在Excel的一個實例中運行的Excel VBA宏是否可以訪問另一個正在運行的Excel實例的工作簿?例如,我想創建一個在任何正在運行的Excel實例中打開的所有工作簿的列表。VBA可以跨越Excel的實例嗎?
回答
科尼利厄斯的答案是部分正確的。他的代碼獲取當前實例,然後創建一個新實例。 GetObject只有獲得第一個實例,無論有多少實例可用。我相信的問題是如何從多個實例中獲得特定的實例。
對於VBA項目,使用一個名爲Command1的命令按鈕創建兩個模塊,一個代碼模塊,另一個作爲窗體。您可能需要添加對Microsoft.Excel的引用。
此代碼顯示立即窗口中每個正在運行的Excel實例的每個工作簿的所有名稱。
'------------- 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
僅供參考,Microsoft Access的類名是'OMain'。我修改了這段代碼,可以輕鬆識別開放的Microsoft Access數據庫。 – AdamsTips 2017-10-27 22:12:37
我不相信這是可能的只使用VBA,因爲您可以訪問的最高級別的對象是應用程序對象,它是當前的Excel實例。
我相信VBA比查爾斯認爲更強大;)
如果只有從GetObject and CreateObject指向特定實例一些棘手的方式,我們將有你的問題解決了!
編輯:
如果你是所有實例的創建者應該有事情像上市工作簿沒有任何問題。看看這個代碼:
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
我認爲,在VBA內,你可以訪問應用程序對象在另一個運行實例。如果您知道在其他實例中打開的工作簿的名稱,則可以獲取對該應用程序對象的引用。見Allen Waytt's page
最後一部分,
Dim xlApp As Excel.Application
Set xlApp = GetObject("c:\mypath\ExampleBook.xlsx").Application
讓我得到一個指針,有ExampleBook.xlsx
開放實例的應用對象。
我相信「ExampleBook」必須是完整的路徑,至少在Excel 2010中。我目前正在嘗試這個,所以我會嘗試更新,因爲我會得到更多的細節。
如果單獨的實例打開相同的工作簿,但只有一個可能具有寫入訪問權限,則可能會有複雜情況發生。
當我使用完整路徑時,+1爲我工作了一個vbs,http://stackoverflow.com/questions/20849853/running-vbscript-function-from-vba/20850129#20850129 – brettdj 2014-01-01 09:25:05
我有一個類似的問題/目標。
而且我得到了ForEachLoops答案,但是需要做出改變。 在底層函數(GetExcelObjectFromHwnd)中,他在兩個debug.print命令中都使用1的工作簿索引。結果是你只能看到第一個WB。
所以我拿了他的代碼,並在GetExcelObjectFromHwnd中放置一個for循環,並將1更改爲一個計數器。結果是我可以獲得所有活動的Excel工作簿,並返回我需要跨Excel實例訪問的信息並訪問其他WB。
我創建了一個類型,以簡化信息的檢索,並將其傳遞迴調用子程序:
Type TargetWBType
name As String
returnObj As Object
returnApp As Excel.Application
returnWBIndex As Integer
End Type
對於名字我只是用的基本文件名,例如「example.xls」。該片段通過在目標WB的每個WS上吐出A6的值來證明該功能。像這樣:
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
所以現在整個模塊foreach循環原先作出這個樣子的,我已經表明我所做的更改。它有一個msgbox彈出窗口,我留在片段中進行調試。一旦找到你的目標,就把它剝掉。代碼:
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
我再說一遍,這個工作,並使用TargetWB類型中的變量,我確實訪問跨Excel的情況下,工作簿和工作表。
我在解決方案中看到的唯一潛在問題是如果您有多個具有相同名稱的WB。現在,我相信它會返回該名稱的最後一個實例。如果我們將Exit For添加到If中,那麼我相信它會返回它的第一個實例。我沒有完全測試這個部分,因爲在我的應用程序中只有一個文件實例處於打開狀態。
感謝這篇不錯的文章,我有一個例程來查找返回當前在機器上運行的所有Excel應用程序的數組。麻煩的是,我剛剛升級到Office 2013 64位,它都出錯了。
有一種將... Declare Function ...
轉換爲... Declare PtrSafe Function ...
的常用方法,這在其他地方已有很好的文檔。但是,我無法找到任何文檔是因爲升級後原始代碼期望的窗口層次結構('XLMAIN' - >'XLDESK' - >'EXCEL7')已更改。對於任何追隨我的腳步的人來說,爲了節省下午的時間,我想我會發布我的更新腳本。這很難測試,但我認爲它應該向後兼容,以便採取更好的措施。
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
你應該根據你的具體情況創建另一個問題情況,然後回答。搜索的人不一定會在這裏找到你的答案。 – guitarthrower 2015-02-13 17:15:21
我明白你的觀點,但我覺得我正在解決原來的問題。自從問題被問到/回答以來,Excel已經開始發展,任何想要用今天的軟件解決問題的人都需要做一些不同的事情。我提供了這個更新,這只是對原始答案的一個小調整。 – 2015-02-13 23:00:32
因爲我也有excele 2°13 64bit,我只會在這裏發表評論:在checkHwnds中發現錯誤:th循環必須停止在n而不是100,因此您需要將n作爲參數傳遞給checkHwnds。 – 2017-10-28 15:32:42
我想補充詹姆斯MacAdie的答案,我想你做的REDIM爲時已晚,因爲在checkHwnds工作,你結束了一個超出範圍的錯誤你盡力檢查值高達100,即使你還沒有完全填充陣列?我修改了下面的代碼,現在它適用於我。
' 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
- 1. ListBox項可以跨越多行嗎? C#
- 2. JTableHeader可以跨越多列嗎?
- 3. RadioGroup可以跨越多個佈局嗎?
- 4. .htaccess,跨越多個Joomla實例的.htpasswd
- 5. 您可以在Excel以外的Excel實例中調用Excel中的Python嗎?
- 6. 防止按鍵跨越實例
- 7. Excel的VBA錯誤越來越oppStatLoc
- 8. iOS支持可跨越字符串嗎?
- 9. PredicateBuilder可以生成跨越多個表的謂詞嗎?
- 10. 跨越wcf的Nhibernate實體
- 11. 我們可以跨越SQL服務器到多臺機器嗎
- 12. 你可以讓Facebook Connect應用跨越多頁嗎?
- 13. Excel VBA自動實例化的神話性能不佳嗎?
- 14. 是否可以跨越一個celltable GWT
- 15. Excel VBA - 使用Dir()的多個實例?
- 16. 在Excel中使用VBA可以訪問VBA代碼嗎?
- 17. 宏完成後VBA保留實例(Excel)
- 18. 類實例可以自毀嗎?
- 19. WPF:BinaryFormatter可以序列化FlowDocument實例嗎?
- 20. 可以合併兩個GraphQLSchema實例嗎?
- 21. JBoss實例可以通信嗎?
- 22. 可以StringBuilder替換一個實例嗎?
- 23. 可以設置實例化嗎?
- 24. Coldfusion CFCs可以從Java實例化嗎?
- 25. 可以重用JDBI DAO實例嗎?
- 26. @compatibility_alias可以調用實例方法嗎?
- 27. 如何讓跨越實例的Y.io全局事件廣播
- 28. Pygame可以超越他們的Rects嗎?
- 29. 硒的webdriver跨越測試用例
- 30. Excel中的VBA函數可以返回一個範圍嗎?
「ForEachLoop」的答案應該是可接受的答案,並賦予「Flakker」功勞。 Cornelius提出的'GetObject'方法不能回答這個問題。 – brettdj 2014-01-01 04:05:41