Option Explicit
#If Win64 Then
Private Const NULL_PTR = 0^
#Else
Private Const NULL_PTR = 0&
#End If
Public Enum EVENTS
ActivateEvent
DeactivateEvent
End Enum
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0& To 7&) As Byte
End Type
#If VBA7 Then
Private Declare PtrSafe Function SetTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr
Private Declare PtrSafe Function KillTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As Long
Private Declare PtrSafe Function IIDFromString Lib "ole32.dll" (ByVal lpsz As LongPtr, lpiid As GUID) As LongPtr
Private Declare PtrSafe Function ObjectFromLresult Lib "oleacc" (ByVal lResult As LongPtr, riid As Any, ByVal wParam As LongPtr, ppvObject As Any) As Long
Private Declare PtrSafe Function GetNextWindow Lib "user32" Alias "GetWindow" (ByVal hwnd As LongPtr, ByVal wFlag As Long) As LongPtr
Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageW" (ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As Any) As Long
Private Declare PtrSafe Function GetActiveWindow Lib "user32" () As LongPtr
Private Declare PtrSafe Function GetCurrentProcessId Lib "kernel32" () As Long
Private Declare PtrSafe Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As LongPtr, lpdwProcessId As Long) As Long
#Else
Private Enum LongPtr
[_]
End Enum
Private Declare Function SetTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr
Private Declare Function KillTimer Lib "user32" (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As Long
Private Declare Function IIDFromString Lib "ole32.dll" (ByVal lpsz As LongPtr, lpiid As GUID) As LongPtr
Private Declare Function ObjectFromLresult Lib "oleacc" (ByVal lResult As LongPtr, riid As Any, ByVal wParam As LongPtr, ppvObject As Any) As Long
Private Declare Function GetNextWindow Lib "user32" Alias "GetWindow" (ByVal hwnd As LongPtr, ByVal wFlag As Long) As LongPtr
Private Declare Function SendMessage Lib "user32" Alias "SendMessageW" (ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As Any) As Long
Private Declare Function GetActiveWindow Lib "user32" () As LongPtr
Private Declare Function GetCurrentProcessId Lib "kernel32" () As Long
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As LongPtr, lpdwProcessId As Long) As Long
#End If
Public Sub StartEventsWatch()
Call KillTimer(Application.hwnd, NULL_PTR)
Call SetTimer(Application.hwnd, NULL_PTR, 0&, AddressOf WatchProc)
End Sub
' _______________________________ PRIVATE HELPER ROUTINES _______________________________
Private Sub StopTimer()
Call KillTimer(Application.hwnd, NULL_PTR)
Debug.Print "timer safely released."
End Sub
Private Sub WatchProc()
Static oPrevForm As Object
Dim oCurForm As Object
Dim lPid As Long
If VBA.UserForms.Count = 0& Then
Call StopTimer: Exit Sub
End If
Call GetWindowThreadProcessId(GetActiveWindow, lPid)
If GetCurrentProcessId <> lPid Then
Exit Sub
End If
On Error Resume Next
Set oCurForm = HwndToDispatch(GetActiveWindow)
If Not (oPrevForm Is oCurForm) Then
Call oPrevForm.UserForms.RaiseEvents(oPrevForm, DeactivateEvent)
Call oCurForm.UserForms.RaiseEvents(oCurForm, ActivateEvent)
End If
On Error GoTo 0
Set oPrevForm = oCurForm
End Sub
Private Function HwndToDispatch(ByVal hwnd As LongPtr) As Object
Const WM_GETOBJECT = &H3D&, OBJID_CLIENT = &HFFFFFFFC
Const GW_CHILD = 5&, S_OK = 0&
Const IID_IDISPATCH = "{00020400-0000-0000-C000-000000000046}"
Dim uGUID As GUID, oDisp As Object
Dim hClient As LongPtr, lResult As Long
hClient = GetNextWindow(hwnd, GW_CHILD)
lResult = SendMessage(hClient, WM_GETOBJECT, NULL_PTR, ByVal OBJID_CLIENT)
If lResult Then
If IIDFromString(StrPtr(IID_IDISPATCH), uGUID) = S_OK Then
If ObjectFromLresult(lResult, uGUID, NULL_PTR, oDisp) = S_OK Then
If Not oDisp Is Nothing Then
Set HwndToDispatch = oDisp
End If
End If
End If
End If
End Function