Attribute VB_Name = "APIWindows"

' Use error handlers in all procedures that make calls to these functions.

' All effort has been made to eliminate errors. Therefore, these functions
' should operate reliably and without any unexpected runtime exceptions, so
' long as you do not pass invalid arguments. If these functions do receive
' invalid arguments then they will indeed raise errors - to let you know.
Public Declare Function APISetFocus Lib "user32" Alias "SetFocus" (ByVal hWnd As Long) As Long
Public Declare Function IsWindow Lib "user32" (ByVal hWnd As Long) As Long
Public Declare Function SetWindowPos Lib "user32" (ByVal hWnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long
Public Declare Function GetForegroundWindow Lib "user32" () As Long
Public Declare Function SetForegroundWindow Lib "user32" (ByVal hWnd As Long) As Long
Public Declare Function MinimizeWindow Lib "user32" Alias "CloseWindow" (ByVal hWnd As Long) As Long
Public Declare Function GetWindow Lib "user32" (ByVal hWnd As Long, ByVal wFlag As Long) As Long
Public Declare Function GetParent Lib "user32" (ByVal hWnd As Long) As Long
Public Declare Function SetParent Lib "user32" (ByVal hWndChild As Long, ByVal hWndNewParent As Long) As Long
Public Declare Function GetTopWindow Lib "user32" (ByVal hWnd As Long) As Long
Public Declare Function GetNextWindow Lib "user32" (ByVal hWnd As Long, ByVal wFlag As Long) As Long
Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Public Declare Function EnumWindows Lib "user32" (ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
Public Declare Function EnumChildWindows Lib "user32" (ByVal hWndParent As Long, 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 nBufLen As Long) As Long
Public Declare Function GetWindowTextLength Lib "user32" Alias "GetWindowTextLengthA" (ByVal hWnd As Long) As Long
Public Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hWnd As Long, ByVal lpString As String, ByVal nBufLen As Long) As Long
Public Declare Function SetWindowText Lib "user32" Alias "SetWindowTextA" (ByVal hWnd As Long, ByVal lpString As String) As Long
Public Declare Function GetWindowPlacement Lib "user32" (ByVal hWnd As Long, lpWndpl As WINDOWPLACEMENT) As Long
Public Declare Function SetWindowPlacement Lib "user32" (ByVal hWnd As Long, lpWndpl As WINDOWPLACEMENT) As Long
Public Declare Function BringWindowToTop Lib "user32" (ByVal hWnd As Long) As Long
Public Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As Long, lpdwProcessId As Long) As Long
Public Declare Function GetCurrentProcessHandle Lib "kernel32" Alias "GetCurrentProcess" () As Long
Public Declare Function GetCurrentProcessId Lib "kernel32" () As Long
Public Declare Function GetCurrentThreadId Lib "kernel32" () As Long
Public Declare Function EnumThreadWindows Lib "user32" (ByVal dwThreadId As Long, ByVal lpFn As Long, ByRef lParam As Long) As Long
Public Declare Function GetModuleFileName Lib "kernel32" Alias "GetModuleFileNameA" (ByVal hModule As Long, ByVal lpFileName As String, ByVal nSize As Long) As Long
Public Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long
Public Declare Function GetModuleHandleZ Lib "kernel32" Alias "GetModuleHandleA" (ByVal hNull As Long) As Long
Public Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long

' Window Field Offsets (Used by GetWindowLong)
Public Const GWL_WNDPROC = (-4)       ' Windows procedure
Public Const GWL_HINSTANCE = (-6)     ' App Instance handle
Public Const GWL_HWNDPARENT = (-8)    ' Use GetParent instead
Public Const GWL_ID = (-12)           ' Window ID (whatever that is)
Public Const GWL_STYLE = (-16)        ' Window style
Public Const GWL_EXSTYLE = (-20)      ' Extended window style
Public Const GWL_USERDATA = (-21)     ' Window style

Private Type POINTAPI
    x As Long
    y As Long
End Type

Private Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

Public Type WINDOWPLACEMENT
    Length As Long
    Flags As Long
    showCmd As Long
    ptMinPosition As POINTAPI
    ptMaxPosition As POINTAPI
    rcNormalPosition As RECT
End Type

' SetWindowPos Flags
Public Const SWP_NOSIZE = &H1                     ' Use existing size, ignores the cx and cy parameters
Public Const SWP_NOMOVE = &H2                     ' Use existing co-ords, ignores the X and Y parameters
Public Const SWP_NOACTIVATE = &H10                ' Does not activate the window.
Public Const SWP_SHOWWINDOW = &H40                ' Displays the window. Note - the window cannot be moved or sized.
Public Const SWP_HIDEWINDOW = &H80                ' Hides the window. Note - the window cannot be moved or sized.

Public Const SWP_FLAGS = SWP_NOMOVE Or SWP_NOSIZE Or SWP_NOACTIVATE

' SetWindowPos hWndInsertAfter value
Public Enum OnTopFlags
    HWND_TOP = 0&
    HWND_BOTTOM = 1&
    HWND_TOPMOST = -1&
    HWND_NOTOPMOST = -2&
End Enum

' GetWindowPlacement and SetWindowPlacement
Public Enum WindowState
    SW_SHOWNORMAL = 1&
    SW_SHOWMINIMIZED = 2&
    SW_SHOWMAXIMIZED = 3&
End Enum

' GetWindow flags
Public Enum WindowRelationship
    GW_HWNDFIRST = 0&
    GW_HWNDLAST = 1&
    GW_HWNDNEXT = 2&
    GW_HWNDPREV = 3&
    GW_OWNER = 4&
    GW_CHILD = 5&
End Enum

' Maximum allowed path length including path, filename,
' and command line arguments for NT (Intel) and Win95
Private Const MAX_PATH As Long = 260
Private Const DIR_SEP As String = "\"

Private Const API_WIND As String = "APIWindows"
Private Const INVALID_hWND As String = "Invalid window handle"

Private Const WINDOW_FOUND = 0&
Private Const WINDOW_NOT_FOUND = 1&

Private Const GET_FIRST = 0&
Private Const GET_ALL = 1&

Private Const FAILED = 0&

Private Const NEG1 = -1&, n0 = 0&, n1 = 1&, n2 = 2&, n3 = 3&, n5 = 5&

Private mWholeCaption As Boolean
Private mWholeClass As Boolean

Private mTargetCaption As String
Private mClassName As String

Private ahWnds() As Long
Private mCount As Long
Private IsBuzy As Boolean

Option Explicit

Public Function GetWindowHandle(Optional WindowCaption As String = vbNullString, Optional ClassName As String = vbNullString, Optional MatchWholeCaption As Boolean, Optional MatchWholeClassName As Boolean) As Long

    ' The WindowCaption argument can be the first part, or all of,
    ' the target window's titlebar caption. Therefore, you could
    ' search for just the main caption (e.g. w/o the appended file
    ' names on MDI form captions). Also, all VB class names begin
    ' with "Thunder", so you could pass this for the ClassName
    ' argument. A VB Projects's class name is "ThunderMain".

    Do While IsBuzy
        DoEvents
    Loop
    Dim rc As Long, hWnd As Long

    ' Either of the first two arguments can be omitted. If both
    ' are omitted the function returns the first window found.

    ' The last arguments specify if the strings passed must match
    ' the window property exactly. If False or omitted just the
    ' first part of the property must match the search string.

    ' If no match was found the function returns zero.

    mWholeCaption = MatchWholeCaption
    mWholeClass = MatchWholeClassName
    mTargetCaption = WindowCaption
    mClassName = ClassName

    ' FindWindow will work only when there are no opened file
    ' names appended to the main caption or omitted, and the
    ' ClassName is exact or omitted.
    hWnd = FindWindow(ClassName, WindowCaption)

    If (hWnd <> n0) Then
        GetWindowHandle = hWnd
    Else
        ' Search all windows for the one we want
        mCount = NEG1
        IsBuzy = True ' Enum call-back function in progress
        On Error Resume Next
        rc = EnumWindows(AddressOf CallBackFunc, GET_FIRST)
        On Error GoTo 0
        IsBuzy = False

        ' If the window was found
        If (rc = WINDOW_FOUND) Then
            If (mCount <> NEG1) Then
                GetWindowHandle = ahWnds(n0)
            End If
        End If
    End If

End Function

Public Function GetAllWindowHandles(Optional WindowCaption As String, Optional ClassName As String, Optional MatchWholeCaption As Boolean, Optional MatchWholeClassName As Boolean = True) As Long()
    Do While IsBuzy
        DoEvents
    Loop
    Dim rc As Long

    ' Either of the first two arguments can be omitted.
    ' If both are omitted the function returns all windows.

    ' The last arguments specify if the strings passed must match
    ' the window property exactly. If False or omitted just the
    ' first part of the property must match the search string.

    ' The function returns a zero based Long array of handles.
    ' If no matchs were found the function returns an empty
    ' array with LBound and UBound properties equal to -1.

    mWholeCaption = MatchWholeCaption
    mWholeClass = MatchWholeClassName
    mTargetCaption = WindowCaption
    mClassName = ClassName

    mCount = NEG1
    ReDim ahWnds(NEG1 To NEG1) As Long

    ' Search all windows
    IsBuzy = True ' Enum call-back function in progress
    On Error Resume Next
    rc = EnumWindows(AddressOf CallBackFunc, GET_ALL)
    On Error GoTo 0
    IsBuzy = False

    ' Return an array of handles
    GetAllWindowHandles = ahWnds()

End Function

' Private support function
Private Function IsInvalid(ByVal hWnd As Long) As Boolean
    If (hWnd = n0) Then
        IsInvalid = True
    Else
        IsInvalid = (IsWindow(hWnd) = FAILED)
    End If
End Function

Public Function GetChildWindowHandle(ByVal hWndParent As Long) As Long
    If IsInvalid(hWndParent) Then Err.Raise n5, API_WIND, INVALID_hWND
    Do While IsBuzy
        DoEvents
    Loop
    Dim rc As Long, hWnd As Long

    hWnd = GetWindow(hWnd, GW_CHILD)

    If (hWnd <> n0) Then
        GetChildWindowHandle = hWnd
    Else
        ' Search all windows for the first one
        mCount = NEG1
        IsBuzy = True ' Enum call-back function in progress
        On Error Resume Next
        rc = EnumChildWindows(hWndParent, AddressOf ChildCallBack, GET_FIRST)
        On Error GoTo 0
        IsBuzy = False

        ' If the window was found
        If (rc = WINDOW_FOUND) Then
            If (mCount <> NEG1) Then
                GetChildWindowHandle = ahWnds(n0)
            End If
        End If
    End If

End Function

Public Function GetAllChildWindowHandles(ByVal hWndParent As Long) As Long()
    If IsInvalid(hWndParent) Then Err.Raise n5, API_WIND, INVALID_hWND
    Do While IsBuzy
        DoEvents
    Loop
    Dim rc As Long

    ' The function returns a zero based Long array of handles.
    ' If no matchs were found the function returns an empty
    ' array with LBound and UBound properties equal to -1.

    mCount = NEG1
    ReDim ahWnds(NEG1 To NEG1) As Long

    ' Search all windows
    IsBuzy = True ' Enum call-back function in progress
    On Error Resume Next
    rc = EnumChildWindows(hWndParent, AddressOf ChildCallBack, GET_ALL)
    On Error GoTo 0
    IsBuzy = False

    ' Return an array of handles
    GetAllChildWindowHandles = ahWnds()

End Function

Public Function SetWindowTopmost(ByVal hWnd As Long, Optional ByVal eOnTop As OnTopFlags = HWND_TOPMOST) As Boolean
    If IsInvalid(hWnd) Then Err.Raise n5, API_WIND, INVALID_hWND

    SetWindowTopmost = SetWindowPos(hWnd, eOnTop, n0, n0, n0, n0, SWP_FLAGS)
End Function

' SetWindowFront

' Sets the specified window into the foreground and activates it.
' The function returns True if it resets the window to the front,
' but returns False if the window was already at the front.
' Also returns False if unsuccessful.

Public Function SetWindowFront(ByVal hWnd As Long) As Boolean
    If IsInvalid(hWnd) Then Err.Raise n5, API_WIND, INVALID_hWND

    ' Set target window to the front
    SetWindowFront = SetForegroundWindow(hWnd)
End Function

Public Function GetWindowFront() As Long
    GetWindowFront = GetForegroundWindow
End Function

' SetWindowToTop

' Sets the specified window to the top and activates it.
' Accepts new window state argument, or normal if omitted.
' Returns True if successful, even if the window was
' already on top. Returns False if unsuccessful.

Public Function SetWindowToTop(ByVal hWnd As Long, Optional ByVal NewState As WindowState = SW_SHOWNORMAL) As Boolean
    If IsInvalid(hWnd) Then Err.Raise n5, API_WIND, INVALID_hWND

    ' Set target window to the top
    Dim rc As Long, Wndpl As WINDOWPLACEMENT

    Wndpl.Length = Len(Wndpl)

    If (GetWindowPlacement(hWnd, Wndpl) <> n0) Then
        ' If currently minimized
        If (Wndpl.showCmd = SW_SHOWMINIMIZED) Then
            ' Set WindowState to new state
            Wndpl.showCmd = NewState

            Wndpl.Length = Len(Wndpl)
            rc = SetWindowPlacement(hWnd, Wndpl)
        End If

        SetWindowToTop = BringWindowToTop(hWnd)
    End If
End Function

Public Function GetWindowState(ByVal hWnd As Long) As WindowState
    If IsInvalid(hWnd) Then Err.Raise n5, API_WIND, INVALID_hWND

    ' Get target window's state
    Dim Wndpl As WINDOWPLACEMENT

    Wndpl.Length = Len(Wndpl)

    If (GetWindowPlacement(hWnd, Wndpl) <> n0) Then
        ' Return the window's current state
        GetWindowState = Wndpl.showCmd
    End If
End Function

Public Function SetWindowState(ByVal hWnd As Long, ByVal NewState As WindowState) As Boolean
    If IsInvalid(hWnd) Then Err.Raise n5, API_WIND, INVALID_hWND

    Dim Wndpl As WINDOWPLACEMENT

    ' First get target window's properties
    Wndpl.Length = Len(Wndpl)

    If (GetWindowPlacement(hWnd, Wndpl) <> n0) Then
        ' Set the window's new state
        Wndpl.showCmd = NewState

        Wndpl.Length = Len(Wndpl)
        SetWindowState = SetWindowPlacement(hWnd, Wndpl)
    End If
End Function

' Centres the window (good for non-VB windows)
Public Function CentreWindow(ByVal hWnd As Long, Optional ByVal AlwaysOnTop As Boolean) As Boolean
    If IsInvalid(hWnd) Then Err.Raise n5, API_WIND, INVALID_hWND

    On Error GoTo AbortPlacement
    Dim wp As WINDOWPLACEMENT
    Dim ww As Long, wh As Long

    ' First get target window's properties
    wp.Length = Len(wp)

    If (GetWindowPlacement(hWnd, wp) <> n0) Then
        ' Set the window's new position
        ww = wp.rcNormalPosition.Right - wp.rcNormalPosition.Left
        wh = wp.rcNormalPosition.Bottom - wp.rcNormalPosition.Top

        wp.rcNormalPosition.Left = ((Screen.Width / Screen.TwipsPerPixelX) - ww) \ n2
        wp.rcNormalPosition.Top = ((Screen.Height / Screen.TwipsPerPixelY) - wh) \ n2

        wp.rcNormalPosition.Right = wp.rcNormalPosition.Left + ww
        wp.rcNormalPosition.Bottom = wp.rcNormalPosition.Top + wh

        If AlwaysOnTop Then
            CentreWindow = SetWindowPos(hWnd, HWND_TOP, wp.rcNormalPosition.Left, _
                                        wp.rcNormalPosition.Top, ww, wh, SWP_SHOWWINDOW)
        Else
            wp.Length = Len(wp)
            CentreWindow = SetWindowPlacement(hWnd, wp)
        End If
    End If
AbortPlacement:
End Function

Public Function GetWindowCaption(ByVal hWnd As Long) As String
    If IsInvalid(hWnd) Then Err.Raise n5, API_WIND, INVALID_hWND
    Dim sWindCapt As String, BufLength As Long

    ' Get maximum length of the window's caption
    BufLength = GetWindowTextLength(hWnd)
    sWindCapt = String$(BufLength, vbNullChar)

    If (GetWindowText(hWnd, sWindCapt, BufLength) <> n0) Then
        GetWindowCaption = TrimZ(sWindCapt)
        ' Note - don't use GetWindowTextLength to trim
        ' the buffers length - see below.
    Else
        GetWindowCaption = "unknown"
    End If

End Function

' Under certain conditions, the GetWindowTextLength function may return a value
' that is larger than the actual length of the text. This occurs with certain
' mixtures of ANSI and Unicode, and is due to the system allowing for the possible
' existence of DBCS characters within the text. The return value, however, will
' always be at least as large as the actual length of the text; you can thus always
' use it to guide buffer allocation. This behavior can occur when an application
' uses both ANSI functions and common dialogs, which use Unicode. It can also occur
' when an application uses the ANSI version of GetWindowTextLength with a window
' whose window procedure is Unicode, or the Unicode version of GetWindowTextLength
' with a window whose window procedure is ANSI. For more information on ANSI and
' Unicode functions, see Win32 Function Prototypes.

Public Function SetWindowCaption(ByVal hWnd As Long, sNewCaption As String) As Boolean
    If IsInvalid(hWnd) Then Err.Raise n5, API_WIND, INVALID_hWND
    Dim sWindCapt As String

    sWindCapt = sNewCaption
    SetWindowCaption = SetWindowText(hWnd, sWindCapt)

End Function

Public Function GetWindowClassName(ByVal hWnd As Long) As String
    If IsInvalid(hWnd) Then Err.Raise n5, API_WIND, INVALID_hWND
    Dim sClassName As String

    ' Allow ample length for the class name
    sClassName = String$(MAX_PATH, vbNullChar)

    If (GetClassName(hWnd, sClassName, MAX_PATH) <> n0) Then
        GetWindowClassName = TrimZ(sClassName)
    Else
        GetWindowClassName = "unknown"
    End If
End Function

Private Function CallBackFunc(ByVal hWnd As Long, ByVal lMode As Long) As Long
    On Error GoTo ErrHandler

    ' Default to Enum the next window
    CallBackFunc = WINDOW_NOT_FOUND

    Dim WCaption As String, WCName As String
    Dim lLen As Long, iCase As Long

    lLen = Len(mTargetCaption)
    If (lLen <> n0) Then
        If mWholeCaption Then
            WCaption = GetWindowCaption(hWnd)
        Else
            WCaption = Left$(GetWindowCaption(hWnd), lLen)
        End If
        iCase = n1
    End If

    lLen = Len(mClassName)
    If (lLen <> n0) Then
        If mWholeClass Then
            WCName = GetWindowClassName(hWnd)
        Else
            WCName = Left$(GetWindowClassName(hWnd), lLen)
        End If
        iCase = iCase + n2
    End If

    Select Case iCase
        Case n0
            mCount = mCount + n1
            ReDim Preserve ahWnds(n0 To mCount) As Long

            ' Record the window handle
            ahWnds(mCount) = hWnd

            ' Set GetHandle mode
            CallBackFunc = lMode

        Case n1
            ' If it's the one we want
            If (WCaption = mTargetCaption) Then

                mCount = mCount + n1
                ReDim Preserve ahWnds(n0 To mCount) As Long

                ' Record the window handle
                ahWnds(mCount) = hWnd

                ' Set GetHandle mode
                CallBackFunc = lMode

            End If

        Case n2
            ' If it's the one we want
            If (WCName = mClassName) Then

                mCount = mCount + n1
                ReDim Preserve ahWnds(n0 To mCount) As Long

                ' Record the window handle
                ahWnds(mCount) = hWnd

                ' Set GetHandle mode
                CallBackFunc = lMode

            End If
    
        Case n3
            ' If it's the one we want
            If (WCaption = mTargetCaption) Then
                If (WCName = mClassName) Then

                    mCount = mCount + n1
                    ReDim Preserve ahWnds(n0 To mCount) As Long

                    ' Record the window handle
                    ahWnds(mCount) = hWnd

                    ' Set GetHandle mode
                    CallBackFunc = lMode

                End If
            End If

    End Select

    Exit Function
ErrHandler:
    ' On error cancel callback
    CallBackFunc = FAILED
End Function

Private Function ChildCallBack(ByVal hWnd As Long, ByVal lMode As Long) As Long
    On Error GoTo ErrHandler

    mCount = mCount + n1
    ReDim Preserve ahWnds(n0 To mCount) As Long

    ' Record the window handle
    ahWnds(mCount) = hWnd

    ' Set GetHandle mode
    ChildCallBack = lMode
    Exit Function
ErrHandler:
    ' On error cancel callback
    ChildCallBack = FAILED
End Function

Public Function GetWindowProcessID(Optional ByVal hWnd As Long) As Long
    Dim ProcessID As Long, lJunk As Long
    If (hWnd <> n0) Then
        If (IsWindow(hWnd) = FAILED) Then Err.Raise n5, API_WIND, INVALID_hWND
        lJunk = GetWindowThreadProcessId(hWnd, ProcessID)
    Else
        ProcessID = GetCurrentProcessId
    End If
    GetWindowProcessID = ProcessID
End Function

Public Function GetWindowThreadID(Optional ByVal hWnd As Long) As Long
    Dim ThreadID As Long, lJunk As Long
    If (hWnd <> n0) Then
        If (IsWindow(hWnd) = FAILED) Then Err.Raise n5, API_WIND, INVALID_hWND
        ThreadID = GetWindowThreadProcessId(hWnd, lJunk)
    Else
        ThreadID = GetCurrentThreadId
    End If
    GetWindowThreadID = ThreadID
End Function

Public Function GetWindowParent(ByVal hWnd As Long) As Long
    If IsInvalid(hWnd) Then Err.Raise n5, API_WIND, INVALID_hWND

    GetWindowParent = GetParent(hWnd)
End Function

Public Function GetWindowRelated(ByVal hWnd As Long, ByVal Relationship As WindowRelationship) As Long
    If IsInvalid(hWnd) Then Err.Raise n5, API_WIND, INVALID_hWND

    GetWindowRelated = GetWindow(hWnd, Relationship)
End Function

Public Function GetClientExeFileSpec() As String
    On Error GoTo ErrHandler
    Dim sModName As String, hInst As Long, rc As Long
    ' Get the application hInstance. By passing NULL, GetModuleHandle
    ' returns a handle to the file used to create the calling process.
    hInst = GetWindowLong(GetModuleHandleZ(n0), GWL_HINSTANCE)
    ' Get the module file name
    sModName = String$(MAX_PATH, vbNullChar)
    rc = GetModuleFileName(hInst, sModName, MAX_PATH)
    GetClientExeFileSpec = TrimZ(sModName)
ErrHandler:
    ' Return empty string on error
End Function

Public Function GetParentExeFileSpec(Optional ByVal hWnd As Long) As String
    Dim sModSpec As String, rc As Long
    Dim hInst As Long, hWndParent As Long
    If (hWnd <> n0) Then
        If IsInvalid(hWnd) Then Err.Raise n5, API_WIND, INVALID_hWND
        ' Get the parent window handle
        hWndParent = GetParent(hWnd)
        Do While (hWndParent)
            hWnd = hWndParent
            hWndParent = GetParent(hWnd)
        Loop
        If (IsWindow(hWnd) = FAILED) Then Err.Raise n5, API_WIND, INVALID_hWND
    Else
        hWnd = GetModuleHandleZ(n0)
    End If
    ' Get the application hInstance
    hInst = GetWindowLong(hWnd, GWL_HINSTANCE)
    ' Get the module file name
    sModSpec = String$(MAX_PATH, vbNullChar)
    rc = GetModuleFileName(hInst, sModSpec, MAX_PATH)
    GetParentExeFileSpec = TrimZ(sModSpec)
End Function

Public Function GetParentExeHandle() As Long
    GetParentExeHandle = GetModuleHandleZ(n0)
End Function

Public Function GetParentExePath(Optional ByVal hWnd As Long) As String
    Dim sModSpec As String, sPath As String
    Dim idx As Long
    sModSpec = GetParentExeFileSpec(hWnd)
    If (LenB(sModSpec) <> n0) Then
        ' Get path by stepping back to the last backslash
        idx = InStrRev(sModSpec, DIR_SEP)
        If (idx <> n0) Then sPath = Left$(sModSpec, idx - n1)
    End If
    GetParentExePath = sPath
End Function

Public Function GetParentExeFileName(Optional ByVal hWnd As Long, Optional GetExtensionOnly As Boolean) As String
    Dim sModSpec As String, sName As String
    Dim idx As Long, sBackSlashOrPeriod As String
    sBackSlashOrPeriod = IIf(GetExtensionOnly, ".", DIR_SEP)
    sModSpec = GetParentExeFileSpec(hWnd)
    If (LenB(sModSpec) <> n0) Then
        ' Get filename by stepping back to the last backslash (or period)
        idx = InStrRev(sModSpec, sBackSlashOrPeriod)
        If (idx <> n0) Then sName = Mid$(sModSpec, idx + n1)
    End If
    GetParentExeFileName = sName
End Function

Public Function GetInstanceHandle(ByVal hWnd As Long) As Long
    If IsInvalid(hWnd) Then Err.Raise n5, API_WIND, INVALID_hWND

    GetInstanceHandle = GetWindowLong(hWnd, GWL_HINSTANCE)
End Function

Public Function GetProjectHandle() As Long
    On Error GoTo ErrHandler
    ' We'll use the main project window handle
    ' ThunderRT6Main (not the VBIDE ThunderMain)
    Dim rc As Long, hWnd As Long

    rc = EnumThreadWindows(App.ThreadID, AddressOf GetProjectMain, hWnd)

    ' If the window was found
    If (rc = WINDOW_FOUND) Then GetProjectHandle = hWnd
ErrHandler:
End Function

' Callback func
Private Function GetProjectMain(ByVal hWnd As Long, ByRef lParam As Long) As Long
    On Error GoTo ErrHandler
    ' Default to Enum the next window
    GetProjectMain = WINDOW_NOT_FOUND

    Dim ClassName As String
    ClassName = GetWindowClassName(hWnd)
    If (Left$(ClassName, 7) = "Thunder") Then

        If (Left$(ClassName, 11) <> "ThunderMain") Then
            ' ThunderRT6Main
            If (InStr(ClassName, "Main") <> n0) Then
                lParam = hWnd
                GetProjectMain = WINDOW_FOUND
            End If
        End If
    End If

    Exit Function
ErrHandler:
    ' On error cancel callback
    GetProjectMain = FAILED
End Function

Private Function TrimZ(StrZ As String) As String
    Dim idx As Long
    idx = InStr(StrZ, vbNullChar)
    If idx <> n0 Then TrimZ = LeftB$(StrZ, idx + idx - n2)
End Function

                                                           '-İRd-
