如何在 VBA 中获取已执行进程的窗口句柄?

发布于 2024-12-10 16:24:49 字数 1059 浏览 0 评论 0原文

在我的 VBA 应用程序中,我启动 IExplore 进程:

Shell sIE, vbMaximizedFocus

现在我需要调整创建的窗口的大小。为此,我可以使用 SetWindowPos 函数,该函数将窗口句柄作为参数之一。我没有那个句柄...

我会使用 FindWindowLike 函数(它会抛出窗口,将标题与模式进行比较,并返回具有匹配标题的窗口句柄数组),但我不能依赖窗口标题。我也不能只调整所有 IE 窗口的大小。

所以,我正在考虑使用一些东西来为我提供刚刚运行的进程的窗口句柄。壳牌不提供这个。

我有一些示例代码,如何使用 CoCreateInstance 函数在 C++ 中执行此操作:

    CoCreateInstance(CLSID_InternetExplorer, NULL, CLSCTX_LOCAL_SERVER, IID_IWebBrowser2, (void**)&m_pBrowser);
        if (m_pBrowser)
        {
            pom  = buffer;
            m_pBrowser->put_Visible(VARIANT_TRUE);
            m_pBrowser->Navigate(pom, &(_variant_t(flaga)), &vDummy, &vDummy, &vDummy);
            m_pBrowser->get_HWND((long *)&hWnd);
            if (hWnd != NULL)
            {
             ...
             ...

我会将其移植到 VBA,但我不太确定第四个参数应该放什么:

里德 [in] 引用用于与对象通信的接口标识符。

好吧,我不知道我应该通过的女巫界面...我什至不确定是否可以在 VBA 中使用它。

所以。有没有一种方法可以执行进程,为我提供其窗口的句柄?

In my VBA application I start IExplore process with:

Shell sIE, vbMaximizedFocus

Now I need to resize created window. For that I can use SetWindowPos function, which takes a handle to the window as one of the arguments. And I don't have that handle...

I would use FindWindowLike function (which goes threw windows, compares caption with pattern and returns array of handles of windows with matching caption), but I cann't rely on window caption. I cann't just resize all of the IE windows also.

So, I was thinking of using SOMETHING that would give me a handle of a window to the process I just ran. Shell does not provide this.

I have some example code, how to do this in C++ using CoCreateInstance function:

    CoCreateInstance(CLSID_InternetExplorer, NULL, CLSCTX_LOCAL_SERVER, IID_IWebBrowser2, (void**)&m_pBrowser);
        if (m_pBrowser)
        {
            pom  = buffer;
            m_pBrowser->put_Visible(VARIANT_TRUE);
            m_pBrowser->Navigate(pom, &(_variant_t(flaga)), &vDummy, &vDummy, &vDummy);
            m_pBrowser->get_HWND((long *)&hWnd);
            if (hWnd != NULL)
            {
             ...
             ...

I would've port this to VBA, but I'm not so sure, what to put for fourth parameter:

riid
[in] Reference to the identifier of the interface to be used to communicate with the object.

Well I don't know witch interface I should pass... I'm not even sure if I can use it in VBA.

So. Is there a way to execute process, which would provide me a handle to it's window?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

把时间冻结 2024-12-17 16:24:49

和蒂姆·威廉姆斯一起去。您可以通过使用 create object 来获取 IE 对象而不是使用 shell 调用来轻松完成此操作。这使得事情变得更容易,因为您可以访问该对象,并且不需要在事后尝试查找窗口句柄。

Global Const SW_MAXIMIZE = 3
Global Const SW_SHOWMINIMIZED = 2
Global Const SW_SHOWNORMAL = 1

Declare Function apiShowWindow Lib "user32" Alias "ShowWindow" _
            (ByVal hwnd As Long, ByVal nCmdShow As Long) As Long

Public Function Test()

    Dim ie As Object

    'reference "Microsoft Internet Controls (ieframe.dll)", and
    'cast ie as "InternetExplorer" if you wish to use intellisense
    Set ie = CreateObject("InternetExplorer.Application")

    ie.Visible = True

    apiShowWindow ie.hwnd, SW_MAXIMIZE
End Function

如果您正在处理多个监视器或需要对窗口进行更多控制,那么事情就会变得更加棘手。这是我之前解决这个问题的答案之一: 是否可以在当前监视器中最大化的 VBA 中启动浏览器窗口?

编辑。将窗口设置到某个位置:

Public Type RECT
   x1 As Long
   y1 As Long
   x2 As Long
   y2 As Long
End Type

Public Enum SetWindowPosFlags
     SWP_ASYNCWINDOWPOS = &H4000
     SWP_DEFERERASE = &H2000
     SWP_DRAWFRAME = &H20
     SWP_FRAMECHANGED = &H20
     SWP_HIDEWINDOW = &H80
     SWP_NOACTIVATE = &H10
     SWP_NOCOPYBITS = &H100
     SWP_NOMOVE = &H2
     SWP_NOOWNERZORDER = &H200
     SWP_NOREDRAW = &H8
     SWP_NOREPOSITION = SWP_NOOWNERZORDER
     SWP_NOSENDCHANGING = &H400
     SWP_NOSIZE = &H1
     SWP_NOZORDER = &H4
     SWP_SHOWWINDOW = &H40
End Enum

Public Enum SpecialWindowHandles
    HWND_TOP = 0
    HWND_BOTTOM = 1
    HWND_TOPMOST = -1
    HWND_NOTOPMOST = -2
End Enum


'taken from IE's ReadyState MSDN Specs
Enum READYSTATE
    READYSTATE_UNINITIALIZED = 0
    READYSTATE_LOADING = 1
    READYSTATE_LOADED = 2
    READYSTATE_INTERACTIVE = 3
    READYSTATE_COMPLETE = 4
End Enum

Declare Function SetWindowPos Lib "user32.dll" (ByVal hWnd As Long, ByVal hWndInsertAfter As SpecialWindowHandles, ByVal X As Integer, ByVal Y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal uFlags As SetWindowPosFlags) As Boolean

Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

Public Sub RunIt()

    Dim ie As Object
    Dim r As RECT

    Set ie = CreateObject("InternetExplorer.Application")

        'draws a 400 pixel x 400 pixel window in position 0 (top left)
    r.x1 = 0
    r.y1 = 0
    r.x2 = r.x1 + 400
    r.y2 = r.y1 + 400

        'HWND_TOP sets the Z Order to our IE Object
        'x2 - x1 ==> Width (In Pixels)
        'y2 - y2 ==> Height (In Pixels)
    SetWindowPos ie.hWnd, HWND_TOP, r.x1, r.y1, (r.x2 - r.x1), (r.y2 - r.y1), SWP_ASYNCWINDOWPOS

    ie.Visible = True
    ie.Navigate "www.google.com"

    'wait until navigated
    Do While ie.Busy Or ie.READYSTATE <> READYSTATE.READYSTATE_COMPLETE
        Sleep 60
    Loop

End Sub

To go along with Tim Williams. You can do it quite easily by using create object to get an IE object vs using a shell call. This makes it easier since you have access to the object, and don't need try to look up the window handle after the fact.

Global Const SW_MAXIMIZE = 3
Global Const SW_SHOWMINIMIZED = 2
Global Const SW_SHOWNORMAL = 1

Declare Function apiShowWindow Lib "user32" Alias "ShowWindow" _
            (ByVal hwnd As Long, ByVal nCmdShow As Long) As Long

Public Function Test()

    Dim ie As Object

    'reference "Microsoft Internet Controls (ieframe.dll)", and
    'cast ie as "InternetExplorer" if you wish to use intellisense
    Set ie = CreateObject("InternetExplorer.Application")

    ie.Visible = True

    apiShowWindow ie.hwnd, SW_MAXIMIZE
End Function

If you are dealing with multiple monitors or need more control over the window, then it gets a little more tricky. Here is one of my previous answers to address that: Is it possible to launch a browser window in VBA maximized in the current monitor?

EDIT. Set window to certain position:

Public Type RECT
   x1 As Long
   y1 As Long
   x2 As Long
   y2 As Long
End Type

Public Enum SetWindowPosFlags
     SWP_ASYNCWINDOWPOS = &H4000
     SWP_DEFERERASE = &H2000
     SWP_DRAWFRAME = &H20
     SWP_FRAMECHANGED = &H20
     SWP_HIDEWINDOW = &H80
     SWP_NOACTIVATE = &H10
     SWP_NOCOPYBITS = &H100
     SWP_NOMOVE = &H2
     SWP_NOOWNERZORDER = &H200
     SWP_NOREDRAW = &H8
     SWP_NOREPOSITION = SWP_NOOWNERZORDER
     SWP_NOSENDCHANGING = &H400
     SWP_NOSIZE = &H1
     SWP_NOZORDER = &H4
     SWP_SHOWWINDOW = &H40
End Enum

Public Enum SpecialWindowHandles
    HWND_TOP = 0
    HWND_BOTTOM = 1
    HWND_TOPMOST = -1
    HWND_NOTOPMOST = -2
End Enum


'taken from IE's ReadyState MSDN Specs
Enum READYSTATE
    READYSTATE_UNINITIALIZED = 0
    READYSTATE_LOADING = 1
    READYSTATE_LOADED = 2
    READYSTATE_INTERACTIVE = 3
    READYSTATE_COMPLETE = 4
End Enum

Declare Function SetWindowPos Lib "user32.dll" (ByVal hWnd As Long, ByVal hWndInsertAfter As SpecialWindowHandles, ByVal X As Integer, ByVal Y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal uFlags As SetWindowPosFlags) As Boolean

Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

Public Sub RunIt()

    Dim ie As Object
    Dim r As RECT

    Set ie = CreateObject("InternetExplorer.Application")

        'draws a 400 pixel x 400 pixel window in position 0 (top left)
    r.x1 = 0
    r.y1 = 0
    r.x2 = r.x1 + 400
    r.y2 = r.y1 + 400

        'HWND_TOP sets the Z Order to our IE Object
        'x2 - x1 ==> Width (In Pixels)
        'y2 - y2 ==> Height (In Pixels)
    SetWindowPos ie.hWnd, HWND_TOP, r.x1, r.y1, (r.x2 - r.x1), (r.y2 - r.y1), SWP_ASYNCWINDOWPOS

    ie.Visible = True
    ie.Navigate "www.google.com"

    'wait until navigated
    Do While ie.Busy Or ie.READYSTATE <> READYSTATE.READYSTATE_COMPLETE
        Sleep 60
    Loop

End Sub
怕倦 2024-12-17 16:24:49

在得到这个问题的明确答案之前,我“谷歌搜索”并测试了“解决方案”很长一段时间。要点是您应该确切地知道您打算用 Hwnd 做什么

原因是,虽然 PID 保持不变,但相应的活动 Hwnd 可能会发生变化 - 例如,当窗口启动[或]最小化时。在这种情况下,我没有成功地从 PID 中找到我正在寻找的“真实”窗口(即恢复它)。请注意,窗口文本标题也可能会更改...(使用下面的测试函数进行检查)

以下函数从 PID 中检索'the' Hwnd(来自 CodeGuru,已修改):

Public Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Public Declare PtrSafe Function GetParent Lib "user32.dll" _
(ByVal hwnd As LongPtr) As LongPtr
Public Declare PtrSafe Function GetWindowThreadProcessId Lib "user32" _
(ByVal hwnd As LongPtr, lpdwProcessId As Long) As Long

Function WndFromPID(ByVal Xpid As LongPtr) As LongPtr
Dim Nhwnd As LongPtr, Npid As Long, Nthread_id As Long
' Get the first window handle.
Nhwnd = FindWindow(vbNullString, vbNullString)  ' NOT (ByVal 0&, ByVal 0& ) !
' Loop until we find the target or we run out of windows
Do While Nhwnd <> 0
    ' See if this window has a parent. If not, it is a top-level window
    If GetParent(Nhwnd) = 0 Then
        ' This is a top-level window. See if it has the target instance handle
        Nthread_id = GetWindowThreadProcessId(Nhwnd, Npid)
        If Npid = Xpid Then
            WndFromPID = Nhwnd      ' This is the target
            Exit Do
        End If
    End If
    Nhwnd = GetWindow(Nhwnd, 2)     ' Examine the next window [2 = GW_HWNDNEXT]
Loop    
End Function

如上所述,这不会返回任何内容如果您的窗口最小化(最终带有另一个文本标题),则很有用。因此,我建议使用部分文本标题(来自VBforum,已修改)来搜索所需的窗口:

Public Declare PtrSafe Function IsWindowVisible Lib "user32" _
(ByVal hwnd As LongPtr) As Long
' *** this is just a flag, you may indeed see an invisible window and vice versa *** 
Public Declare PtrSafe Function GetWindow Lib "user32" _
(ByVal hwnd As LongPtr, ByVal wCmd As Long) As LongPtr
Public Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" _
(ByVal hwnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As Long
Public Declare PtrSafe Function GetWindowTextLength Lib "user32" _
Alias "GetWindowTextLengthA" (ByVal hwnd As LongPtr) As Long
Public Function FindWindowHandle(PartialTitle As String, Sichtbar As Byte) As LongPtr
Dim lhWndP As LongPtr
If GetHandleFromPartialCaption(lhWndP, PartialTitle) = True Then
    Select Case Sichtbar
        Case 0              ' invisible
            If IsWindowVisible(lhWndP) = False Then
                ' MsgBox "INVISIBLE Window Handle: " & lhWndP, vbOKOnly + vbInformation
                FindWindowHandle = lhWndP
            End If
        Case 1              ' visible
            If IsWindowVisible(lhWndP) = True Then
                ' MsgBox "VISIBLE Window Handle: " & lhWndP, vbOKOnly + vbInformation
                FindWindowHandle = lhWndP
            End If
        Case Else           ' all
            FindWindowHandle = lhWndP
            ' MsgBox "Window Handle: " & lhWndP, vbOKOnly + vbInformation
    End Select
Else
    ' MsgBox "No Window '" & PartialTitle & "…' found!", vbOKOnly + vbExclamation
End If
End Function

Private Function GetHandleFromPartialCaption(ByRef lWnd As LongPtr, _
ByVal sCaption As String) As Boolean  ' = subfunction ofFindWindowHandle()  
Dim lhWndP As LongPtr, sStr As String, GW_HWNDNEXT As Byte
GW_HWNDNEXT = 2  
GetHandleFromPartialCaption = False
lhWndP = FindWindow(vbNullString, vbNullString) 'PARENT WINDOW
Do While lhWndP <> 0
    sStr = String(GetWindowTextLength(lhWndP) + 1, Chr$(0))
    GetWindowText lhWndP, sStr, Len(sStr)
    sStr = Left$(sStr, Len(sStr) - 1)
    If InStr(1, sStr, sCaption) > 0 Then
        GetHandleFromPartialCaption = True
        lWnd = lhWndP
        Exit Do
    End If
    lhWndP = GetWindow(lhWndP, GW_HWNDNEXT)
Loop
End Function

因此,请检查在您的特定情况下哪种更适合您。您可以使用以下测试子(只需替换您的应用程序即可):

Sub WindowTitle()  
Dim MyAppPID As LongPtr, WndHdl As LongPtr
Dim Dummy, WinText As String, MaxChar As Long
MaxChar = 255
WinText = String(MaxChar, " ")
MyAppPID = Shell("C:\Program Files (x86)\MyPhoneExplorer\" _
    & "MyPhoneExplorer.exe action=connect flags=fromjumplist", vbHide)
'    MyAppPID = Shell("C:\Program Files (x86)\CodeTwo\QR Code Desktop" _
    & " Reader & Generator\CodeTwo QR Code Desktop Reader & Generator.exe")    
Debug.Print "PID-method:"
WndHdl = WndFromPID(MyAppPID)  ' *** not always the desired window ***
Debug.Print MyAppPID & " ——> " & WndHdl
Dummy = GetWindowText(WndHdl, WinText, MaxChar)
Debug.Print IIf(Dummy > 0, WinText, "————————")    
Debug.Print "alternative method:"
WndHdl = FindWindowHandle("MyPhone", 2)     ' 2 = all windows
'    WndHdl = FindWindowHandle("CodeTwo", 2)
Debug.Print MyAppPID & " ——> " & WndHdl
Dummy = GetWindowText(WndHdl, WinText, MaxChar)
Debug.Print IIf(Dummy > 0, WinText, "————————")    
Call Shell("C:\Windows\SysWOW64\taskkill.exe /f /pid " & MyAppPID)        
End Sub

I 'googled' and tested the 'solutions' quite a long time before getting a clear answer for this issue. The essential point is that you should exactly know what you intend to do with the Hwnd !

The reason is that while the PID remains constant, the corresponding active Hwnd may change - for example when the window starts [or gets] minimized. In such case, I didn't success in finding the 'real' window I was looking for from the PID (i.e. to restore it). Note that the window text caption may change, too… (check using test function below)

The following function retrieves 'the' Hwnd from the PID (from CodeGuru, modified):

Public Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Public Declare PtrSafe Function GetParent Lib "user32.dll" _
(ByVal hwnd As LongPtr) As LongPtr
Public Declare PtrSafe Function GetWindowThreadProcessId Lib "user32" _
(ByVal hwnd As LongPtr, lpdwProcessId As Long) As Long

Function WndFromPID(ByVal Xpid As LongPtr) As LongPtr
Dim Nhwnd As LongPtr, Npid As Long, Nthread_id As Long
' Get the first window handle.
Nhwnd = FindWindow(vbNullString, vbNullString)  ' NOT (ByVal 0&, ByVal 0& ) !
' Loop until we find the target or we run out of windows
Do While Nhwnd <> 0
    ' See if this window has a parent. If not, it is a top-level window
    If GetParent(Nhwnd) = 0 Then
        ' This is a top-level window. See if it has the target instance handle
        Nthread_id = GetWindowThreadProcessId(Nhwnd, Npid)
        If Npid = Xpid Then
            WndFromPID = Nhwnd      ' This is the target
            Exit Do
        End If
    End If
    Nhwnd = GetWindow(Nhwnd, 2)     ' Examine the next window [2 = GW_HWNDNEXT]
Loop    
End Function

As explained above, this returns nothing useful if your window is minimized (eventually with another text caption). Hence, I recommend to search for the desired window using a partial text caption (from VBforum, modified):

Public Declare PtrSafe Function IsWindowVisible Lib "user32" _
(ByVal hwnd As LongPtr) As Long
' *** this is just a flag, you may indeed see an invisible window and vice versa *** 
Public Declare PtrSafe Function GetWindow Lib "user32" _
(ByVal hwnd As LongPtr, ByVal wCmd As Long) As LongPtr
Public Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" _
(ByVal hwnd As LongPtr, ByVal lpString As String, ByVal cch As Long) As Long
Public Declare PtrSafe Function GetWindowTextLength Lib "user32" _
Alias "GetWindowTextLengthA" (ByVal hwnd As LongPtr) As Long
Public Function FindWindowHandle(PartialTitle As String, Sichtbar As Byte) As LongPtr
Dim lhWndP As LongPtr
If GetHandleFromPartialCaption(lhWndP, PartialTitle) = True Then
    Select Case Sichtbar
        Case 0              ' invisible
            If IsWindowVisible(lhWndP) = False Then
                ' MsgBox "INVISIBLE Window Handle: " & lhWndP, vbOKOnly + vbInformation
                FindWindowHandle = lhWndP
            End If
        Case 1              ' visible
            If IsWindowVisible(lhWndP) = True Then
                ' MsgBox "VISIBLE Window Handle: " & lhWndP, vbOKOnly + vbInformation
                FindWindowHandle = lhWndP
            End If
        Case Else           ' all
            FindWindowHandle = lhWndP
            ' MsgBox "Window Handle: " & lhWndP, vbOKOnly + vbInformation
    End Select
Else
    ' MsgBox "No Window '" & PartialTitle & "…' found!", vbOKOnly + vbExclamation
End If
End Function

Private Function GetHandleFromPartialCaption(ByRef lWnd As LongPtr, _
ByVal sCaption As String) As Boolean  ' = subfunction ofFindWindowHandle()  
Dim lhWndP As LongPtr, sStr As String, GW_HWNDNEXT As Byte
GW_HWNDNEXT = 2  
GetHandleFromPartialCaption = False
lhWndP = FindWindow(vbNullString, vbNullString) 'PARENT WINDOW
Do While lhWndP <> 0
    sStr = String(GetWindowTextLength(lhWndP) + 1, Chr$(0))
    GetWindowText lhWndP, sStr, Len(sStr)
    sStr = Left$(sStr, Len(sStr) - 1)
    If InStr(1, sStr, sCaption) > 0 Then
        GetHandleFromPartialCaption = True
        lWnd = lhWndP
        Exit Do
    End If
    lhWndP = GetWindow(lhWndP, GW_HWNDNEXT)
Loop
End Function

Thus, please check what suits you better in your particular case. You may use the following test sub (just replace the apps by yours):

Sub WindowTitle()  
Dim MyAppPID As LongPtr, WndHdl As LongPtr
Dim Dummy, WinText As String, MaxChar As Long
MaxChar = 255
WinText = String(MaxChar, " ")
MyAppPID = Shell("C:\Program Files (x86)\MyPhoneExplorer\" _
    & "MyPhoneExplorer.exe action=connect flags=fromjumplist", vbHide)
'    MyAppPID = Shell("C:\Program Files (x86)\CodeTwo\QR Code Desktop" _
    & " Reader & Generator\CodeTwo QR Code Desktop Reader & Generator.exe")    
Debug.Print "PID-method:"
WndHdl = WndFromPID(MyAppPID)  ' *** not always the desired window ***
Debug.Print MyAppPID & " ——> " & WndHdl
Dummy = GetWindowText(WndHdl, WinText, MaxChar)
Debug.Print IIf(Dummy > 0, WinText, "————————")    
Debug.Print "alternative method:"
WndHdl = FindWindowHandle("MyPhone", 2)     ' 2 = all windows
'    WndHdl = FindWindowHandle("CodeTwo", 2)
Debug.Print MyAppPID & " ——> " & WndHdl
Dummy = GetWindowText(WndHdl, WinText, MaxChar)
Debug.Print IIf(Dummy > 0, WinText, "————————")    
Call Shell("C:\Windows\SysWOW64\taskkill.exe /f /pid " & MyAppPID)        
End Sub
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文