Find Top Window
-
- StarLounger
- Posts: 93
- Joined: 04 Mar 2010, 16:32
Find Top Window
What API can I use to find which window is on top at a particular location? I have a macro to perform a given number of mouse clicks at a given frequency, but it no longer works in one instance because the mouse-clicks cause short-lived pop-up windows. So, before I start the macro, I need to get the handle of the window on top. Then before I programmatically click, check who is on top and wait for any other windows to disappear. TIA!
-
- StarLounger
- Posts: 93
- Joined: 04 Mar 2010, 16:32
Re: Find Top Window
I suppose that I could use a different algorithm: mouse click (which will activate the app) and GetActiveWindow. Then before the next mouse click, GetActiveWindow and skip the click if it's not the original window. I'll try it.
-
- StarLounger
- Posts: 93
- Joined: 04 Mar 2010, 16:32
Re: Find Top Window
Looks like it should be GetForegroundWindow instead of GetActiveWindow
-
- Administrator
- Posts: 78481
- Joined: 16 Jan 2010, 00:14
- Status: Microsoft MVP
- Location: Wageningen, The Netherlands
-
- 4StarLounger
- Posts: 550
- Joined: 27 Jun 2021, 10:46
Re: Find Top Window
API call that specifically answers your requirement to "find which window is on top at a particular location?" is WindowFromPoint, eg
Code: Select all
Option Explicit
Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Private Declare Function WindowFromPoint Lib "user32" (ByVal X As Long, ByVal Y As Long) As Long
Private Type POINTAPI
X As Long
Y As Long
End Type
Private mousePT As POINTAPI
Public Function WindowUnderCursor() As Long
GetCursorPos mousePT
WindowUnderCursor = WindowFromPoint(mousePT.X, mousePT.Y)
End Function
Public Sub test()
MsgBox WindowUnderCursor
End Sub
-
- 5StarLounger
- Posts: 1100
- Joined: 21 Jan 2011, 16:51
- Location: Florida
Re: Find Top Window
What's the declaration statements for a 64-bit system? Excel tells me I need to revise the statements for 64-bit and add PTRSAFE.
PJ in (usually sunny) FL
-
- 4StarLounger
- Posts: 550
- Joined: 27 Jun 2021, 10:46
Re: Find Top Window
Private Declare PtrSafe Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Private Declare PtrSafe Function WindowFromPoint Lib "user32" (ByVal X As Long, ByVal Y As Long) As Long
Private Declare PtrSafe Function WindowFromPoint Lib "user32" (ByVal X As Long, ByVal Y As Long) As Long
-
- StarLounger
- Posts: 93
- Joined: 04 Mar 2010, 16:32
Re: Find Top Window
Argggggggg, I loved SpeakEasy's WindowFromPoint solution, but it did not catch the stupid pop-up window: it gave me a number, but always the same one even though there were 11 times when the pop-up window received the click. Unfortunately, I cannot repeat it: I have no control over the program that I am clicking in. But, Google is your friend, just discovered that on 64-bit Windows, WindowFromPoint only takes one argument, a POINTAPI. I should have looked that up before writing the code (and should have told you, sorry). By tomorrow, I should be able to test it again. Tough to debug when you only get one try per day. I'll let you know tomorrow. Thanks!
Just noticed there is a question re declaration. This is the one that I will use.
No guarantee on that: cannot test it until tomorrow.
Just noticed there is a question re declaration. This is the one that I will use.
Code: Select all
Public Type POINTAPI
x As Long
y As Long
End Type
Public Declare PtrSafe Function WindowFromPoint Lib "user32" (ByRef lpPoint As POINTAPI) As Long
-
- StarLounger
- Posts: 93
- Joined: 04 Mar 2010, 16:32
Re: Find Top Window
and that is the same as what SpeakEasy said, so it will probably work
-
- 4StarLounger
- Posts: 550
- Joined: 27 Jun 2021, 10:46
Re: Find Top Window
>Google is your friend, just discovered that on 64-bit Windows, WindowFromPoint only takes one argument, a POINTAPI. I should have looked that up before writing the code
Just because Google says it doesn't neccessarily make it true ...
Here's the thing. Whilst the formal API declaration in winuser.h does indeed indicate that the call takes a single POINT parameter, that is a C++ declaration, and as it stands it won't work in VBA. Why? Because we need to pass ByVal, and VBA cannot pass UDTs ByVal. The correct declaration for VBA is the one I gave.
Just because Google says it doesn't neccessarily make it true ...
Here's the thing. Whilst the formal API declaration in winuser.h does indeed indicate that the call takes a single POINT parameter, that is a C++ declaration, and as it stands it won't work in VBA. Why? Because we need to pass ByVal, and VBA cannot pass UDTs ByVal. The correct declaration for VBA is the one I gave.
-
- StarLounger
- Posts: 93
- Joined: 04 Mar 2010, 16:32
Re: Find Top Window
Wow, good information! But, I thought that if you did not specify how the parameter is passed (and you did not), then it is passed by reference. And, that would mean that our declarations were the same. At least I can test this out. I will write a test macro & post it here. Thanks!
-
- 4StarLounger
- Posts: 550
- Joined: 27 Jun 2021, 10:46
Re: Find Top Window
>and you did not
I didn't for GetCursorPos because that function does NOT require the UDT to be passed ByVal. (because they are [out] parameters if you check the C ++ declaration)
But that isn't the API call being called into question; both my declarations (32bit and 64bit) for WindowFromPoint are clearly using ByVal (since they are [in] parameters)
And if you try to use
Public Declare PtrSafe Function WindowFromPoint Lib "user32" (ByRef lpPoint As POINTAPI) As Long
you'll get compile error 'bad DLL calling convention' error
Change it to ByVal, and you'll get a 'User-defined type may not be passed ByVal' error
I didn't for GetCursorPos because that function does NOT require the UDT to be passed ByVal. (because they are [out] parameters if you check the C ++ declaration)
But that isn't the API call being called into question; both my declarations (32bit and 64bit) for WindowFromPoint are clearly using ByVal (since they are [in] parameters)
And if you try to use
Public Declare PtrSafe Function WindowFromPoint Lib "user32" (ByRef lpPoint As POINTAPI) As Long
you'll get compile error 'bad DLL calling convention' error
Change it to ByVal, and you'll get a 'User-defined type may not be passed ByVal' error
-
- StarLounger
- Posts: 93
- Joined: 04 Mar 2010, 16:32
Re: Find Top Window
Be patient, lol. I am super picky about API declarations. Here is my test program. Application.hWnd, WindowFromPoint, and GetForegroundWindow all return the same value, so either WindowFromPoint or GetForegroundWindow should work in my clicker macro.
Code: Select all
Option Explicit
Private Type POINTAPI
x As Long
y As Long
End Type
Private Declare PtrSafe Function GetCursorPos Lib "user32" ( _
ByRef lpPoint As POINTAPI) As Long
Private Declare PtrSafe Sub mouse_event Lib "user32" ( _
ByVal dwFlags As Long, _
ByVal dx As Long, _
ByVal dy As Long, _
ByVal cButtons As Long, _
ByVal dwExtraInfo As Long)
Private Const MOUSEEVENTF_LEFTDOWN = &H2 ' left button down
Private Const MOUSEEVENTF_LEFTUP = &H4 ' left button up
Private Const MOUSEEVENTF_MIDDLEDOWN = &H20 ' middle button down
Private Const MOUSEEVENTF_MIDDLEUP = &H40 ' middle button up
Private Const MOUSEEVENTF_RIGHTDOWN = &H8 ' right button down
Private Const MOUSEEVENTF_RIGHTUP = &H10 ' right button up
Private Declare PtrSafe Function WindowFromPoint Lib "user32" ( _
ByRef lpPoint As POINTAPI) As Long
Private Declare PtrSafe Function GetForegroundWindow Lib "user32" () As Long
Public Sub WhoIsOnFirst()
Dim p As POINTAPI, hX As Long, hC As Long, hT As Long
MsgBox "Move the cursor over the Title Bar of an Excel Window & press the Enter Key"
GetCursorPos p
' Click the mouse & activate the excel window
mouse_event MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
mouse_event MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
hX = Application.hwnd
hC = WindowFromPoint(p)
hT = GetForegroundWindow
Debug.Print hX, hC, hT
End Sub[\code]
-
- 4StarLounger
- Posts: 550
- Joined: 27 Jun 2021, 10:46
Re: Find Top Window
>Application.hWnd, WindowFromPoint, and GetForegroundWindow all return the same value
Is this your assumption, or have you tested this? I ask because
a) the code you present should not run without errors.
b) Even if the code worked, I would not expect the results to be the same for each of the methods (in particular, I would suggest that GetForegroundWindow does not do what you think it is doing; it has nothing directly to do with the window's position in the z-order), although in your specific example here Application.hWnd and GetForegroundWindow will return the same result. Applying GetAncestor to the result returned from WindowFromPoint with gaFlags set to GA_ROOT should get the same result as GetForegroundWindow
Is this your assumption, or have you tested this? I ask because
a) the code you present should not run without errors.
b) Even if the code worked, I would not expect the results to be the same for each of the methods (in particular, I would suggest that GetForegroundWindow does not do what you think it is doing; it has nothing directly to do with the window's position in the z-order), although in your specific example here Application.hWnd and GetForegroundWindow will return the same result. Applying GetAncestor to the result returned from WindowFromPoint with gaFlags set to GA_ROOT should get the same result as GetForegroundWindow
-
- 5StarLounger
- Posts: 817
- Joined: 24 Jan 2010, 15:56
Re: Find Top Window
Per the MS docs, for 64bit applications:
Code: Select all
#If Win64 Then
Declare PtrSafe Function WindowFromPoint Lib "user32" Alias "WindowFromPoint" (ByVal Point As LongLong) As LongPtr
' Copies a POINTAPI into a LongLong. For an API requiring a ByVal POINTAPI parameter,
' this LongLong can be passed in instead. Example API's include WindowFromPoint,
' ChildWindowFromPoint, ChildWindowFromPointEx, DragDetect, and MenuItemFromPoint.
Function PointToLongLong(point As POINTAPI) As LongLong
Dim ll As LongLong
Dim cbLongLong As LongPtr
cbLongLong = LenB(ll)
' make sure the contents will fit
If LenB(point) = cbLongLong Then
CopyMemory ll, point, cbLongLong
End If
PointToLongLong = ll
End Function
#Else
Regards,
Rory
Rory
-
- 4StarLounger
- Posts: 550
- Joined: 27 Jun 2021, 10:46
Re: Find Top Window
Yep, that should work!
-
- StarLounger
- Posts: 93
- Joined: 04 Mar 2010, 16:32
Re: Find Top Window
Thanks Rory. That explains why I was having so much trouble using WindowFromPoint. I ended up using GetForegroundWindow. It also has issues: "The foreground window can be NULL in certain circumstances, such as when a window is losing activation," which is exactly what is happening when there is a popup. Fortunately that is when I don't want to count the mouse click. So, I wrote a function:
iStopLoop is a module-level global. I stuck it in because I figured that I would probable have an infinite loop, but so far that hasn't happened.
I also had issues with getting the wrong handle especially on the first click, so I started with clicking until I got the same handle twice in a row, then just counted clicks with that handle. That got me close, just off by several clicks & close is good enough. I just don't want to wear out my hand clicking. Here is the macro:
Thanks everyone for your help! Sorry it took so long to get back with the solution, but this is not for work & work is currently a monstrous macro.
Code: Select all
Private Function ClickAndGetHandle(iMilli As Long) As Long
Dim h As Long
mouse_event MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0
mouse_event MOUSEEVENTF_LEFTUP, 0, 0, 0, 0
h = GetForegroundWindow
Do While h = 0
iStopLoop = iStopLoop + 1
Debug.Print "Skip #" & iStopLoop
If iStopLoop > 200 Then End
Sleep iMilli \ 2
h = GetForegroundWindow
Loop
ClickAndGetHandle = h
End Function
I also had issues with getting the wrong handle especially on the first click, so I started with clicking until I got the same handle twice in a row, then just counted clicks with that handle. That got me close, just off by several clicks & close is good enough. I just don't want to wear out my hand clicking. Here is the macro:
Code: Select all
Public Sub ClickerNoPopup()
Dim cp1 As POINTAPI, cp2 As POINTAPI
Dim n As Long, s As String
s = InputBox("Enter number of clicks, move to position, & press the Enter key")
n = CInt(s)
' In case instructions were not followed, wait until cursor stops
Do
GetCursorPos cp1
Sleep 500
GetCursorPos cp2
Loop Until cp1.x = cp2.x And cp1.y = cp2.y
Sleep 500
Dim i As Long, iMilli As Long
iMilli = 100
' Make sure we have the correct handle
iStopLoop = 0
Dim h1 As Long, h2 As Long
Do
h1 = ClickAndGetHandle(iMilli)
Debug.Print "h1", h1
h2 = ClickAndGetHandle(iMilli)
Debug.Print "h2", h2
Loop Until h1 = h2
For i = 1 To n - 2
Do
h2 = ClickAndGetHandle(iMilli)
Debug.Print "h ", h2
Loop Until h1 = h2
Sleep iMilli / 2
Next i
End Sub
-
- StarLounger
- Posts: 93
- Joined: 04 Mar 2010, 16:32
Re: Find Top Window
Well, I spoke too soon: had an infinite loop (or would have had one without iStopLoop). Looks like the window handle changed or I had the wrong handle initially. Can that happen?
As I said above, testing is difficult. I only get a buildup of a need for 100 mouse clicks per day & it takes about 20 clicks before popups start, so stopping the loop at 200 wiped out my chances for another test. Maybe I can figure it out tomorrow.
As I said above, testing is difficult. I only get a buildup of a need for 100 mouse clicks per day & it takes about 20 clicks before popups start, so stopping the loop at 200 wiped out my chances for another test. Maybe I can figure it out tomorrow.
-
- StarLounger
- Posts: 93
- Joined: 04 Mar 2010, 16:32
Re: Find Top Window
Well, for now, we will never know the answer. The vendor that was throwing up the annoying pop-ups fixed their code, so my mouse clicker works great. Now, if I could finish my massive data mining macros, I will be happy.
-
- Administrator
- Posts: 78481
- Joined: 16 Jan 2010, 00:14
- Status: Microsoft MVP
- Location: Wageningen, The Netherlands