Reputation: 1137
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?
Upvotes: 0
Views: 6249
Reputation: 73
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
Upvotes: 1
Reputation: 3436
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
Upvotes: 2