如何迭代多个 Word 实例(使用 AccessibleObjectFromWindow) [英] How to iterate over multiple Word instances (with AccessibleObjectFromWindow)

查看:19
本文介绍了如何迭代多个 Word 实例(使用 AccessibleObjectFromWindow)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要遍历所有 Word 实例,无论是用户打开的、自动化的、zumbis 等打开的.

我将描述到目前为止的所有步骤:我在

有趣的是,我在Excel输出中只能找到两个字符串:XLMAIN和XLDESK,却完全找不到EXCEL7,成功捕获了Excel对象.对于 Word,我测试了所有字符串 (_WwC,_WwO,),但只有

?FindWindowEx(hWinWord, 0&, "_WwF", vbNullString)1185896? FindWindowEx(hWinDesk, 0&, "_WwB", vbNullString)5707422

按顺序得到一个句柄;但无济于事,因为

 ?AccessibleObjectFromWindow(hWin7, OBJID_NATIVEOM, iid, obj)-2147467259

有什么想法吗?路线?

解决方案

在按照@Comintern 的建议与 Spy++ 更加亲密之后,我追踪了这个:

这是实际的Window顺序;OpusApp 下的所有窗口都是它的子窗口

但要了解它为什么现在起作用,我们必须右键单击下面的每个 _Ww[A_Z]:

对于_WwF:

为了它的孩子_WwB:

终于达到了目标!!!!!_工作组:

使用这种方法,很明显我们必须在代码中添加另一层:

 Function GetWordapp(hWinWord As Long, wordApp As Object) As BooleanDim hWinDesk 一样长,hWin7 一样长,hFinalWindow 一样长Dim obj 作为对象Dim iid 作为 GUID调用 IIDFromString(StrPtr(IID_IDispatch), iid)hWinDesk = FindWindowEx(hWinWord, 0&, "_WwF", vbNullString)hWin7 = FindWindowEx(hWinDesk, 0&, "_WwB", vbNullString)hFinalWindow = FindWindowEx(hWin7, 0&, "_WwG", vbNullString)如果 AccessibleObjectFromWindow(hFinalWindow, OBJID_NATIVEOM, iid, obj) = S_OK 然后设置 wordApp = obj.ApplicationGetWordapp = 真万一结束函数

我不明白但现在不介意的是为什么对 2 个不同的实例重复结果:调试.打印结果:

 Instance_1 1972934x - fatores reumaticos.docxFormGerenciadorCentralPacientes.docm实例_2 11010524x - fatores reumaticos.docxFormGerenciadorCentralPacientes.docm实例_3 4857668

但为了解决这个问题,我会调整 奇迹解决方案 @PGS62;继续:

私有函数 GetWordInstances() 作为集合Dim AlreadyThere As 布尔值将 wd 调暗为应用程序设置 GetWordInstances = 新集合...代码...对于 GetWordInstances 中的每个 wd如果 wd 是 WordApp.Application 那么已经存在 = 真退出万一下一个如果还没有那么GetWordInstances.Add WordApp.Application万一...代码...结束函数

而且,瞧,为大众迭代所有 Word 实例,而无需关闭和重新打开!!!

感谢社区,感谢其他线程中的所有想法,以及@Comintern 的重要建议.

I need to iterate over all Word instances, no matter if opened by users, by automation, zumbis, etc.

I will describe all the steps until now: I saw and implemented the solutions that I got here;

       Do
            For Each objWordDocument In objWordApplication.Documents
               OpenDocs(iContadorDocs - 1) = objWordDocument.Name
               OpenDocs(iContadorDocs) = objWordDocument.path
               iContadorDocs = iContadorDocs + 2
               ReDim Preserve OpenDocs(iContadorDocs)
            Next objWordDocument
            iWordInstances = iWordInstances + 1
            objWordApplication.Quit False
            Set objWordApplication = Nothing
            Set objWordApplication = GetObject(, "Word.Application")
       Loop While Not objWordApplication Is Nothing

it works, but:

  1. for iterate over all word instances we have to GetObject and close it, looping until no more opened instances are, and after, reopen all that I care

    • this take a lot of time & R/W cycles & Disk Access

    • and of course has to be accomplished outside Word, because it may close the code running instance first, or in the middle of the loop...

So, after some googling, I saw some examples of access the process direct, here and here for VB.

I managed to get the PID for all Winword.exe instances, mainly adapting a little the code at VBForums:

Showing only the modified piece of code:

   Do
        If LCase(VBA.Left$(uProcess.szExeFile, InStr(1, uProcess.szExeFile, Chr(0)) - 1)) = LCase(ProcessName) Then
            ProcessId = uProcess.th32ProcessID
            Debug.Print "Process name: " & ProcessName & "; Process ID: " & ProcessId
        End If
   Loop While ProcessNext(hSnapShot, uProcess)

For the above code run, we need the PROCESSENTRY32 structure that include both process name (szExeFile) and Process Id fields (th32ProcessID); this code is @ VBnet/Randy Birch.

So, now I have the word instances PIDs; what next?

After doing that, I tried to see how could I pass these PID instances to the GetObject function.

At this time I bumped into this Python thread, that opened my eyes to the AccessibleObjectFromWindow that creates an object from a windows handle.

I dug in a lot of places, the most useful being these here, here and here and could get this piece of code:

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 IIDFromString Lib "ole32" _
        (ByVal lpsz As Long, ByRef lpiid As GUID) As Long
Private Declare Function AccessibleObjectFromWindow Lib "oleacc" _
        (ByVal hWnd As Long, ByVal dwId As Long, ByRef riid As GUID, _
         ByRef ppvObject As Object) As Long

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(7) As Byte
End Type
Private Const S_OK As Long = &H0
Private Const IID_IDispatch As String = "{00020400-0000-0000-C000-000000000046}"
Private Const OBJID_NATIVEOM As Long = &HFFFFFFF0

Sub testWord()
Dim i As Long
Dim hWinWord As Long
Dim wordApp As Object
Dim doc As Object
    'Below line is finding all my Word instances
    hWinWord = FindWindowEx(0&, 0&, "OpusApp", vbNullString)
    While hWinWord > 0
        i = i + 1
        '########Successful output
        Debug.Print "Instance_" & i; hWinWord
        '########Instance_1 2034768 
        '########Instance_2 3086118 
        '########Instance_3 595594 
        '########Instance_4 465560 
        '########Below is the problem
        If GetWordapp(hWinWord, wordApp) Then
            For Each doc In wordApp.documents
                Debug.Print , doc.Name
            Next
        End If
        hWinWord = FindWindowEx(0, hWinWord, "OpusApp", vbNullString)
    Wend
End Sub

Function GetWordapp(hWinWord As Long, wordApp As Object) As Boolean
Dim hWinDesk As Long, hWin7 As Long
Dim obj As Object
Dim iid As GUID

    Call IIDFromString(StrPtr(IID_IDispatch), iid)
    hWinDesk = FindWindowEx(hWinWord, 0&, "_WwF", vbNullString)
   '########Return 0 for majority of classes; only for _WwF it returns other than 0
    hWin7 = FindWindowEx(hWinDesk, 0&, "_WwB", vbNullString)
   '########Return 0 for majority of classes; only for _WwB it returns other than 0
    If AccessibleObjectFromWindow(hWin7, OBJID_NATIVEOM, iid, obj) = S_OK Then
   '########Return -2147467259 and does not get object...
        Set wordApp = obj.Application
        GetWordapp = True
    End If
End Function

The errors are commented (########) above into the code; but resuming, I identify all instances, but cannot retrieve the object. For Excel, the lines:

hWinDesk = FindWindowEx(hWinXL, 0&, "XLDESK", vbNullString)
hWin7 = FindWindowEx(hWinDesk, 0&, "EXCEL7", vbNullString)

works, because instead of zero I got hWinDesk = 1511272 and 332558, and after I got the Excel object.

The EXCEL7 corresponding Word Windows class is _WwG (but it gives 0 above), the XLMAIN corresponding Word class name is OpusApp. What is the XLDESK corresponding for Word?

So, I need help to discover it; or do you know how to capture the COM object in VBA knowing it's PID? MS itself suggested that I look into the Office 200 docs; I'll do that, but if someone has did this before...

In fact now I'm interested in both approaches, but of course this last one is 99% implemented, so, my preferred.

TIA

P.S. Of course, when implemented, all objects will be closed/nothing, error-handling, etc...

EDIT 1: Here is Spy++ output, as per @Comintern advise:

Interesting is that I can locate in Excel output only two of the strings: XLMAIN and XLDESK, but cannot find at all the EXCEL7, and Excel object is successfully captured. For Word, I tested all the strings (_WwC,_WwO,), but only

?FindWindowEx(hWinWord, 0&, "_WwF", vbNullString)
 1185896 
?FindWindowEx(hWinDesk, 0&, "_WwB", vbNullString)
 5707422 

got a handle, in that order; but to no avail, because

 ?AccessibleObjectFromWindow(hWin7, OBJID_NATIVEOM, iid, obj)
-2147467259 

Any ideas? directions?

解决方案

After getting more intimate with Spy++ as @Comintern suggested, I traced this:

This is the actual Window order; all windows below the OpusApp are its children

But to understand why it is functioning now, we have to right click every _Ww[A_Z] below:

For _WwF:

For its children _WwB:

And finally to the goal!!!!! _WwG:

With this approach, it is obvious that we must add another layer to the code:

  Function GetWordapp(hWinWord As Long, wordApp As Object) As Boolean
        Dim hWinDesk As Long, hWin7 As Long, hFinalWindow As Long
        Dim obj As Object
        Dim iid As GUID

        Call IIDFromString(StrPtr(IID_IDispatch), iid)
        hWinDesk = FindWindowEx(hWinWord, 0&, "_WwF", vbNullString)
        hWin7 = FindWindowEx(hWinDesk, 0&, "_WwB", vbNullString)
        hFinalWindow = FindWindowEx(hWin7, 0&, "_WwG", vbNullString)
        If AccessibleObjectFromWindow(hFinalWindow, OBJID_NATIVEOM, iid, obj) = S_OK Then
            Set wordApp = obj.Application
            GetWordapp = True
        End If
    End Function

What I don't understand, but don't mind now, is why duplicate results for 2 different instances: Debug.print results:

   Instance_1 1972934 
                  x - fatores reumaticos.docx
                  FormGerenciadorCentralPacientes.docm
    Instance_2 11010524 
                  x - fatores reumaticos.docx
                  FormGerenciadorCentralPacientes.docm
    Instance_3 4857668 

But to solve that, I'll adapt the marvel solution by @PGS62; resuming:

Private Function GetWordInstances() As Collection
    Dim AlreadyThere As Boolean
    Dim wd As Application
    Set GetWordInstances = New Collection
    ...code...
    For Each wd In GetWordInstances 
                If wd Is WordApp.Application Then
                    AlreadyThere = True
                    Exit For
                End If
            Next
            If Not AlreadyThere Then
                GetWordInstances.Add WordApp.Application
            End If
      ...code...
End Function

And, voilá, iteration for all Word instances for the masses without have to close and reopen!!!

Thanks, community, for all ideas in other threads, and @Comintern for the crucial advise.

这篇关于如何迭代多个 Word 实例(使用 AccessibleObjectFromWindow)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆