'In MS Office VBA (VSTO), how can I track when the window gains/looses focus?

I am currently developing a PowerPoint VSTO addin in VBA. I would like to receive events when the windows gains/looses focus.

I understand that :

  • There is no "LostFocus" or "GainedFocus" event in VSTO
  • There is an event "WindowActivate", but it only works when the user switched from one Office Window to another - it doesn't trigger if the user went to another program (e.g.,focus is lost).
  • It is therefore required to use Windows Hooks (SetWinEventHook)

However, when trying to pass on the address of the callback function (4th parameter), I encounter an error AddressOf cannot be converted to Long because Long is not a delegate (see full code bellow).

I have unsuccesfully tried several options to solve this issue:

  • Casting the Address into an interger (CInt64(), CIntPtr())
  • Create a delegate for the callback function (see code below).

None of this works. Would any of you know how to make this hook work so that I can track when the window gains/looses focus?

Best,

JB

Public Class FocusTracker

    Implements IDisposable

    ' Declare all const values and variables required to set up the hook
    Private Const EVENT_SYSTEM_FOREGROUND = &H3&
    Private Const WINEVENT_OUTOFCONTEXT = 0
    Private handlColl As Collection

    ' Declare all references to external procedures in Windows' DLLs
    Private Declare Function SetWinEventHook Lib "user.dll" (ByVal eventMin As Long,
                ByVal eventMax As Long, ByVal hmodWinEventProc As Long, ByVal pfnWinEventProc As Long,
                ByVal idProcess As Long, ByVal idThread As Long, ByVal dwFlags As Long) As Long
    Private Declare Function GetCurrentProcessId Lib "kernel" () As Long
    Private Declare Function GetWindowThreadProcessId Lib "user" (ByVal hWnd As Long, lpdwProcessId As Long) As Long
    Private Declare Function UnhookWinEvent Lib "user.dll" (ByVal hWinEventHook As Long) As Long

    ' Initializer of the object 
    Public Sub New()
        StartFocusMonitoring()
    End Sub

    ' Sub that will try to create the hook
    Public Function StartFocusMonitoring() As IntPtr

        ' Create the collection object to store the handles      
        If handlColl Is Nothing Then handlColl = New Collection

        ' Need to create a "Delegate" to send the address of the callback function to Windows (cf. declaration below)
        Dim del As Object = New WinEventFuncDelegate(AddressOf WinEventFunc)
        
        ' Try to hook Windows. This is where the error comes from.
        StartFocusMonitoring = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, 0&,
                                        del, 0, 0, WINEVENT_OUTOFCONTEXT)


        handlColl.Add(StartFocusMonitoring)
    End Function

    ' Try to create a Delegate so that SetWindowsHook accepts the AddressOf value
    Public Delegate Function WinEventFuncDelegate(ByVal HookHandle As Long, ByVal LEvent As Long,
                            ByVal hWnd As Long, ByVal idObject As Long, ByVal idChild As Long,
                            ByVal idEventThread As Long, ByVal dwmsEventTime As Long) As Long

    ' The callback function
    Public Function WinEventFunc(ByVal HookHandle As Long, ByVal LEvent As Long,
                            ByVal hWnd As Long, ByVal idObject As Long, ByVal idChild As Long,
                            ByVal idEventThread As Long, ByVal dwmsEventTime As Long) As Long
        On Error Resume Next
        Dim thePID As Long

        If LEvent = EVENT_SYSTEM_FOREGROUND Then
            GetWindowThreadProcessId(hWnd, thePID)
            If thePID = GetCurrentProcessId Then
                'Do something
            Else
                ' Do something else
            End If
        End If
        On Error GoTo 0
    End Function



Solution 1:[1]

You need to use the SetWindowsHookEx function which installs an application-defined hook procedure into a hook chain. You would install a hook procedure to monitor the system for certain types of events. These events are associated either with a specific thread or with all threads in the same desktop as the calling thread.

In VSTO add-ins use the Marshal.GetFunctionPointerForDelegate method which converts a delegate into a function pointer that is callable from unmanaged code. Note, you must manually keep the delegate from being collected by the garbage collector from managed code. The garbage collector does not track references to unmanaged code.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Eugene Astafiev