1

I am writing a VBA program that needs to wait until a specific window is open.

I tried FindFindow from the user32.dll.
Even if I set the two parameters of the function to Null, I get a negative return, although in that case all windows should match.
Basically I don't get a result different from 0 for hwnd independent of how I call FindWindow.

Declare Function FindWindow Lib "user32" _
  (ByVal lpClassName As String, ByVal lpWindowName As String) As Long

Sub Main
    Dim hwnd As Long
    hwnd = FindWindow(vbNullString, vbNullString)
    If (hwnd = 0) Then MsgBox ("failure")
End Sub

Solutions to similar problems like How to use FindWindow to find a visible or invisible window with a partial name in VBA don't seem to work.

Community
  • 1
  • 1
Lorenz Kummer
  • 75
  • 3
  • 11

3 Answers3

0

The problem is that vbNullString is a length 0 string, the same as "". When that is marshalled to the unmanaged code a non-null pointer to a null-terminated array of characters is passed. Because the length is 0 then a pointer to a null-terminator is passed. That's different from NULL which is the null pointer.

I'm far from a VBA expert, but I think the solution is like so:

Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
    (ByRef lpClassName As Any, ByRef lpWindowName As Any) As Long

If you want to call this passing NULL for both arguments do so like this:

window = FindWindow(ByVal 0&, ByVal 0&)

Alternatively, if you want to pass a string value do it like this:

window = FindWindow(ByVal 0&, ByVal "Untitled - Notepad")
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • This gives me a Type mismatch error. Also i the Syntax using vbNullString has been reported a working for other users. Example. https://stackoverflow.com/questions/25098263/how-to-use-findwindow-to-find-a-visible-or-invisible-window-with-a-partial-name The solution provided in that thread doesnt work either. – Lorenz Kummer Mar 09 '18 at 09:11
  • It's working here. Although I'm using 64 bit Excel. Note that I excised the `PtrSafe` in my `Declare`. I can't see your exact error message or where it occurs, so I can't comment on that. Details matter. – David Heffernan Mar 09 '18 at 09:19
  • I get the following erros: https://i.imgur.com/l8T7SEQ.png or https://i.imgur.com/IE8eNyi.png and https://i.imgur.com/YI4UCq1.png depending on how i implement the function. – Lorenz Kummer Mar 09 '18 at 09:58
  • Works fine for me in 32 bit Excel 2010. Sounds like you have a less capable VBA implementation. – David Heffernan Mar 09 '18 at 10:01
  • Ok so it 's possibly got to do something with the environment i am running the script in. I am running it in Dragon NaturallySperaking 15. – Lorenz Kummer Mar 09 '18 at 10:06
  • Yeah, I'm pretty sure that's the crucial information. I'm sure that the issue is what I explained already. That `vbNullString` does not marshal as `NULL`. But what the solution in your VBA is I cannot tell. If you always want to pass `NULL` to the same argument, you can declare it as `ByVal lpXXX As Long` and pass `0&`. That would allow you to prove that the issue is what I say. You can use `Alias` to declare a variety of combinations of imports, so I think that should get you home. – David Heffernan Mar 09 '18 at 10:09
  • 2
    This answer is completely wrong. `vbNullString` is supposed to, and is marshalled as, `nullptr`. The problem with the OP's code is that it does not run because there is no function called `FindWindow` in user32 - there is `FindWindowA` and `FindWindowW`. If the OP had shown the actual code it would have worked as expected. It certainly does for me, `FindWindow(vbNullString, vbNullString)` finds the first window handle like it should. – GSerg Mar 16 '23 at 08:45
-1

I hope that a find a solution for your problem. To declare the Null value required by the Function in VBA change the parameter types to Integer and in the Function call use 0. Like this

Declare Function FindWindow Lib "user32" Alias "FindWindowA" _

  (ByVal a As Integer, ByVal b As Integer) As Long

and

hwnd = FindWindow(0, 0)

I think it will help.

avariant
  • 2,234
  • 5
  • 25
  • 33
Black cat
  • 1,056
  • 1
  • 2
  • 11
  • 1
    Not only is this completely wrong, as `Integer` is 16 bits in VBA, it is also completely unnecessary as `vbNullString` is the correct way to pass null string. – GSerg Mar 16 '23 at 08:39
  • @gserg The result for the generated String is exact for the process in user32. At the first 0 byte process is ended. vbNullString is a constant for sg. "probably" a single 0 byte. – Black cat Mar 29 '23 at 06:27
-1

Add these declarations, subroutines, and functions to a Module

Public Declare Function ShowWindow Lib "user32.dll" (ByVal Hwnd As Long, ByVal nCmdShow As Long) As Long

Public Declare Function GetWindowHandle Lib "user32" (ByVal Hwnd As Long, ByVal wCmd As Long) As Long

Public Declare Function GetWindow Lib "user32" (ByVal Hwnd As Long, ByVal wCmd As Long) As Long

Public Declare Function IsWindowEnabled Lib "user32" (ByVal Hwnd As Long) As Long

Public Declare Function GetParent Lib "user32" (ByVal Hwnd As Long) As Long

Public Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal Hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long

Public Declare Function EnumWindows Lib "user32" (ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long

Public Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal Hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long

Public Const GW_HWNDNEXT As Long = 2
Public Const MAX_PATH = 260

Public Sub GetWindowInfo(Hwnd As Long, Optional sTitle As String, Optional sClass As String)

set up the strings to receive the Process Class and Window Title

sTitle = Space$(MAX_PATH)
sClass = Space$(MAX_PATH)

Run GetClassName api function and return the Process Class

Call GetClassName(Hwnd, sClass, MAX_PATH)

Run GetWindowText api function and return the Process Title

Call GetWindowText(Hwnd, sTitle, MAX_PATH)

strip the trailing chr$(0)'s from the strings returned above

sClass = TrimNull(sClass)
sTitle = TrimNull(sTitle)

Run IsWindowVisible api function to find visible windows

If IsWindowVisible(Hwnd) = 1 Then   'window visible is true
    'Debug.Print "hwnd value is  " & hwnd
    'Debug.Print "hwnd Process Class is  " & sClass
    'Debug.Print "hwnd Process Title is  " & sTitle
    'Debug.Print "hwnd that is visible " & IsWindowVisible(hwnd)
    'Debug.Print ""
End If
End Sub

Subroutine to Launch a URL using a shell command string

Public Sub MS_Edge_Launch(URL)

Create and run a Shell object to launch a separate window, Window Style = 1 for visible, Wait for return is True (wait for it to finish before executing further code)

Dim WshShell As Object
Dim CommandString As String
Set WshShell = VBA.CreateObject("WScript.Shell")
CommandString = """C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"" --start-fullscreen -new-window " & """" & URL & """"
WshShell.Run CommandString, 1, True
End Sub

Maximize Window Subroutine and Function

Public Sub InitEnumWindowsMaximizeByWindowTitle()
Debug.Print "Maximize by Window Title Initial Subroutine started"
Debug.Print "Looking for '" & WindowTitle & "' in the Window Title."
EnumWindows AddressOf EnumWindowProcMaximizeByWindowTitle, &H0
End Sub

Public Function EnumWindowProcMaximizeByWindowTitle(ByVal Hwnd As Long, ByVal lParam As Long) As Long
Dim sTitle  As String
Dim sClass  As String
Dim hWndProcessID As Long
GetWindowInfo Hwnd, sTitle, sClass

get the ThreadProcessID of the window

Call GetWindowThreadProcessId(Hwnd, hWndProcessID)

Find a specific window title

If sTitle Like "*" & WindowTitle & "*" Then
    Debug.Print "Found '" & WindowTitle & "' In the Window Title.  " & "Window hwnd = " & Hwnd & " Window Title = " & sTitle
    ShowWindow Hwnd, SW_SHOWMAXIMIZED
    Debug.Print "Window Maximized"
End If
'To continue enumeration, return True
'To stop enumeration return False (0).
'When 1 is returned, enumeration continues until there are no more windows left.
EnumWindowProcMaximizeByWindowTitle = 1
End Function

Put it all in to action by running this subroutine

Public Sub LaunchURL()
Dim URL As String
URL = "https://stackoverflow.com/questions/49189701/how-to-wait-until-a-specific-window-is-open"
'Call the subroutine
Call MS_Edge3.MS_Edge_Launch(URL)
'Maximize the Window
WindowTitle = "vba - How to wait until a specific"
InitEnumWindowsMaximizeByWindowTitle
End Sub
Mohammad Reza Mousavi
  • 894
  • 1
  • 10
  • 18