Imports System.Text

Public Class ClassWindowAPI
    Const WM_GETTEXT As Int32 = &HD
    Const WM_GETTEXTLENGTH As Int32 = &HE

    Public Class ApiWindow
        Public MainWindowTitle As String = ""
        Public ClassName As String = ""
        Public hWnd As Int32
    End Class

    Public Class WindowInfo
        Public WindowTitle As String = ""
        Public ProcessName As String = ""
        Public ClassName As String = ""
        Public ProcessId As Integer = 0
        Public ControlName As String = ""
        Public hWnd As IntPtr
    End Class

    Private Delegate Function EnumCallBackDelegate(ByVal hwnd As Integer, ByVal lParam As Integer) As Integer

    Private Declare Function GetCurrentThreadId Lib "kernel32.dll" Alias "GetCurrentThreadId" () As IntPtr
    Private Declare Function AttachThreadInput Lib "user32.dll" Alias "AttachThreadInput" (ByVal idAttach As IntPtr, ByVal idAttachTo As IntPtr, fAttach As Boolean) As Boolean
    Private Declare Function GetFocus Lib "user32.dll" Alias "GetFocus" () As IntPtr
    Private Declare Function GetForegroundWindow Lib "user32.dll" Alias "GetForegroundWindow" () As IntPtr
    Private Declare Function GetWindowThreadProcessId Lib "user32.dll" (ByVal hwnd As IntPtr, ByRef lpdwProcessID As Integer) As Integer
    Private Declare Function GetWindowText Lib "user32.dll" Alias "GetWindowTextA" (ByVal hWnd As IntPtr, ByVal WinTitle As String, ByVal MaxLength As Integer) As Integer
    Private Declare Function GetWindowTextLength Lib "user32.dll" Alias "GetWindowTextLengthA" (ByVal hwnd As Int32) As Integer

    ' Top-level windows.
    Private Declare Function EnumWindows Lib "user32" (ByVal lpEnumFunc As EnumCallBackDelegate, ByVal lParam As Integer) As Integer

    ' Child windows.
    Private Declare Function EnumChildWindows Lib "user32" (ByVal hWndParent As Integer, ByVal lpEnumFunc As EnumCallBackDelegate, ByVal lParam As Integer) As Integer

    ' Get the window class.
    Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Integer, ByVal lpClassName As StringBuilder, ByVal nMaxCount As Integer) As Integer

    ' Test if the window is visible--only get visible ones.
    Private Declare Function IsWindowVisible Lib "user32" (ByVal hwnd As Integer) As Integer

    ' Test if the window's parent--only get the one's without parents.
    Private Declare Function GetParent Lib "user32" (ByVal hwnd As Integer) As Integer

    ' Get window text length signature.
    Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Int32, ByVal wMsg As Int32, ByVal wParam As Int32, ByVal lParam As Int32) As Int32

    ' Get window text signature.
    Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Int32, ByVal wMsg As Int32, ByVal wParam As Int32, ByVal lParam As StringBuilder) As Int32

    Private _listChildren As New List(Of WindowInfo)
    Private _listTopLevel As New List(Of WindowInfo)

    Private _topLevelClass As String = ""
    Private _childClass As String = ""

    ''' <summary>
    ''' Get all top-level window information
    ''' </summary>
    ''' <returns>List of window information objects</returns>
    Public Overloads Function GetTopLevelWindows() As List(Of WindowInfo)

        EnumWindows(AddressOf EnumWindowProc, &H0)

        Return _listTopLevel

    End Function

    Public Overloads Function GetTopLevelWindows(ByVal className As String) As List(Of WindowInfo)
        _topLevelClass = className

        Return GetTopLevelWindows()
    End Function

    ''' <summary>
    ''' Get all child windows for the specific windows handle (hwnd).
    ''' </summary>
    ''' <returns>List of child windows for parent window</returns>
    Public Overloads Function GetChildWindows(ByVal hwnd As Int32) As List(Of WindowInfo)
        ' Clear the window list.
        _listChildren = New List(Of WindowInfo)

        ' Start the enumeration process.
        EnumChildWindows(hwnd, AddressOf EnumChildWindowProc, &H0)

        ' Return the children list when the process is completed.
        Return _listChildren
    End Function

    Public Overloads Function GetChildWindows(ByVal hwnd As Int32, ByVal childClass As String) As List(Of WindowInfo)
        ' Set the search
        _childClass = childClass

        Return GetChildWindows(hwnd)
    End Function

    ''' <summary>
    ''' Callback function that does the work of enumerating top-level windows.
    ''' </summary>
    ''' <param name="hwnd">Discovered Window handle</param>
    ''' <returns>1=keep going, 0=stop</returns>
    Private Function EnumWindowProc(ByVal hwnd As Int32, ByVal lParam As Int32) As Int32

        ' Eliminate windows that are not top-level.
        If GetParent(hwnd) = 0 AndAlso CBool(IsWindowVisible(hwnd)) Then

            ' Get the window title / class name.
            Dim oWindow As WindowInfo = GetWindowInfo(hwnd)

            ' Match the class name if searching for a specific window class.
            If _topLevelClass.Length = 0 OrElse oWindow.ClassName.ToLower() = _topLevelClass.ToLower() Then
                _listTopLevel.Add(oWindow)
            End If

        End If

        ' To continue enumeration, return True (1), and to stop enumeration 
        ' return False (0).
        ' When 1 is returned, enumeration continues until there are no 
        ' more windows left.
        Return 1
    End Function

    ''' <summary>
    ''' Callback function that does the work of enumerating child windows.
    ''' </summary>
    ''' <param name="hwnd">Discovered Window handle</param>
    ''' <returns>1=keep going, 0=stop</returns>
    Private Function EnumChildWindowProc(ByVal hwnd As Int32, ByVal lParam As Int32) As Int32

        Dim oWindow As WindowInfo = GetWindowInfo(hwnd)

        ' Attempt to match the child class, if one was specified, otherwise
        ' enumerate all the child windows.
        If _childClass.Length = 0 OrElse oWindow.ClassName.ToLower() = _childClass.ToLower() Then
            _listChildren.Add(oWindow)
        End If

        Return 1

    End Function

    ''' <summary>
    ''' Returns Information about the Window with `hWnd`
    ''' </summary>
    Public Shared Function GetWindowInfo(ByVal hWnd As Integer) As WindowInfo
        Dim oPID As Integer = 0
        Dim oTitleLength As Int32 = GetWindowTextLength(hWnd)
        Dim oWindowTitle As String = StrDup(oTitleLength + 1, "*")
        Dim oClassBuilder As New StringBuilder(64)

        GetWindowText(hWnd, oWindowTitle, oTitleLength + 1)
        GetWindowThreadProcessId(hWnd, oPID)

        If oPID = 0 Then
            Return Nothing
        End If

        Dim oProcess As Process = Process.GetProcessById(oPID)

        If oProcess Is Nothing Then
            Return Nothing
        End If

        GetClassName(hWnd, oClassBuilder, 64)

        Return New WindowInfo With {
            .hWnd = hWnd,
            .ClassName = oClassBuilder.ToString,
            .ProcessId = oProcess.Id,
            .ProcessName = oProcess.ProcessName,
            .WindowTitle = oWindowTitle.Replace(vbNullChar, String.Empty)
        }
    End Function

    ''' <summary>
    ''' Returns Information about the currently focused window
    ''' </summary>
    Public Shared Function GetWindowInfo() As WindowInfo
        Dim hWnd As IntPtr = GetForegroundWindow()
        If hWnd = IntPtr.Zero Then
            Return Nothing
        End If

        Return GetWindowInfo(hWnd)
    End Function

    ''' <summary>
    ''' 
    ''' </summary>
    ''' <param name="WindowHandle">Current window handle; can be obtained from Me.Handle</param>
    ''' <returns></returns>
    Public Shared Function GetFocusedControl(WindowHandle As IntPtr) As WindowInfo
        Try
            Dim oWindow = GetWindowInfo()

            If oWindow Is Nothing Then
                Return Nothing
            End If

            Dim oThreadId As IntPtr = GetWindowThreadProcessId(oWindow.hWnd, IntPtr.Zero)
            Dim oMyThreadId As IntPtr = GetWindowThreadProcessId(WindowHandle, IntPtr.Zero)

            If AttachThreadInput(oThreadId, oMyThreadId, True) Then
                Try
                    Dim oControlhWnd = GetFocus()
                    Dim oControl As WindowInfo = GetWindowInfo(oControlhWnd)

                    If oControl Is Nothing Then
                        Return Nothing
                    End If

                    Dim oUtils As New ClassWindowAPIUtils()
                    Dim oName = oUtils.GetWinFormsId(oControlhWnd)
                    oControl.ControlName = oName

                    Return oControl
                Catch ex As Exception
                    Logger.Error(ex)
                Finally
                    AttachThreadInput(oThreadId, oMyThreadId, False)
                End Try
            End If

            Return Nothing
        Catch ex As Exception
            Logger.Error(ex)
            Return Nothing
        End Try

    End Function
    Public Shared Function FocusedControlinActiveWindow(WindowHandle As IntPtr) As IntPtr
        Try
            Dim activewindowhandle As IntPtr = GetForegroundWindow
            Dim activewindowThread As IntPtr = GetWindowThreadProcessId(activewindowhandle, IntPtr.Zero)
            Dim thiswindowthread As IntPtr = GetWindowThreadProcessId(WindowHandle, IntPtr.Zero)
            AttachThreadInput(activewindowThread, thiswindowthread, True)
            Dim focusedControlHandle As IntPtr = GetFocus()
            AttachThreadInput(activewindowThread, thiswindowthread, False)
            Return focusedControlHandle
        Catch ex As Exception
            Return Nothing
        End Try

    End Function
End Class
