Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

vb.net find messagebox in another application using FindWindowex

I've got a visual basic application that needs to find Microsoft Access which has a message box and then Send Enter to the message box.

I followed this post (FindWindow FindWindowEx).

It finds Access and brings it to the foreground but it doesn't want to find the message box and bring it to the front:

enter image description here

Public Class Form1

 Private Declare Function SetForegroundWindow Lib "user32" (ByVal hwnd As IntPtr) As Long

 Private Declare Auto Function FindWindow Lib "user32.dll" ( _
 ByVal lpClassName As String, _
 ByVal lpWindowName As String _
 ) As IntPtr

 Private Declare Auto Function FindWindowEx Lib "user32.dll" ( _
 ByVal hwndParent As IntPtr, _
 ByVal hwndChildAfter As IntPtr, _
 ByVal lpszClass As String, _
 ByVal lpszWindow As String _
 ) As IntPtr

 Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
  Dim hWnd As IntPtr
  hWnd = FindWindow("OMain", Nothing)

  MsgBox(hWnd) 'FINDS 1640402

  Dim hWndChild1 As IntPtr = _
  FindWindowEx(hWnd, IntPtr.Zero, "#32770 (Dialog)", "Microsoft Access")

  MsgBox(hWndChild1) 'FIRST PROBLEM IT FINDS ZERO HERE

  Dim hWndChild1Button As IntPtr = _
  FindWindowEx(hWndChild1, IntPtr.Zero, "Button", "OK")

  MsgBox(hWndChild1Button) 'ALSO FINDS ZERO HERE

  If hWndChild1Button <> IntPtr.Zero Then
   SetForegroundWindow(hWndChild1Button)
   SendKeys.SendWait("{Enter}")
  End If

 End Sub
End Class

enter image description here enter image description here

enter image description here enter image description here

like image 848
Wilest Avatar asked Nov 15 '16 10:11

Wilest


1 Answers

The code is not using the correct winapi function. FindWindowEx() can find child windows, but the window displayed by MsgBox() is not a child window. It is a top-level window, the kind you could find with FindWindow().

But that function isn't good enough to locate the specific message box you want to close. A better approach is needed, one you could use is enumerating the windows that are owned by the same thread with EnumThreadWindows(). Nice thing about MsgBox() is that there will be only ever one such window since the dialog box is modal.

SendKeys() is not exacty good enough either, it only ever works properly if the message box is in the foreground. A much better approach is to actually click the button by sending it the BM_CLICK message. Tested code, using an Access form:

Imports System.Runtime.InteropServices
Imports System.ComponentModel
Imports System.Text

Module Module1
    Sub Main()
        '' Find the MS-Access host window
        Dim access = FindWindow("OMain", Nothing)
        If access = IntPtr.Zero Then Throw New Win32Exception()
        '' Enumerate the windows owned by the same thread
        Dim pid As Integer
        Dim tid = GetWindowThreadProcessId(access, pid)
        If tid = 0 Then Throw New Win32Exception()
        EnumThreadWindows(tid, AddressOf ClickOkButton, Nothing)
    End Sub

    Private Function ClickOkButton(hWnd As IntPtr, lp As IntPtr) As Boolean
        '' Verify the class name is #32770
        Dim buf As New StringBuilder(256)
        GetClassName(hWnd, buf, 256)
        If buf.ToString <> "#32770" Then Return True
        '' Find the OK button (control ID 2)
        Dim okbutton = GetDlgItem(hWnd, 2)
        If okbutton = IntPtr.Zero Then Return True
        '' Activate the dialog, just in case
        SetActiveWindow(hWnd)
        '' Click the button
        SendMessage(okbutton, BM_CLICK, IntPtr.Zero, IntPtr.Zero)
        '' Done, no need to continue enumerating windows
        Return False
    End Function
End Module

Friend Module NativeMethods
    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
    Friend Function FindWindow(ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
    End Function

    <DllImport("user32.dll", SetLastError:=True)>
    Friend Function GetWindowThreadProcessId(ByVal hwnd As IntPtr, ByRef lpdwProcessId As Integer) As Integer
    End Function

    Friend Delegate Function EnumThreadDelegate(hWnd As IntPtr, lParam As IntPtr) As Boolean

    <DllImport("user32.dll", SetLastError:=True)>
    Friend Function EnumThreadWindows(dwThreadId As Int32, lpfn As EnumThreadDelegate, lParam As IntPtr) As Boolean
    End Function

    <DllImport("user32.dll", CharSet:=CharSet.Auto)>
    Friend Function GetClassName(ByVal hWnd As System.IntPtr, ByVal lpClassName As System.Text.StringBuilder, ByVal nMaxCount As Integer) As Integer
    End Function

    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
    Friend Function GetDlgItem(ByVal hDlg As IntPtr, id As Integer) As IntPtr
    End Function

    <DllImport("user32.dll", SetLastError:=True)>
    Friend Function SetActiveWindow(ByVal hWnd As IntPtr) As IntPtr
    End Function

    <DllImport("user32.dll")>
    Friend Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wp As IntPtr, ByVal lp As IntPtr) As IntPtr
    End Function

    Friend Const BM_CLICK As Integer = &HF5
End Module

Usual advice is to favor UI Automation.

like image 127
Hans Passant Avatar answered Nov 14 '22 21:11

Hans Passant