16

Is there a way to be notified of when focus changes from any window to another window(even between windows applications) such that I can just have my delegate called immediately when the user changes focus??

I keep thinking I may just have to do polling :( :( every 1 second and call GetForegroundWindow but I really don't want to do that.

Deanna
  • 23,876
  • 7
  • 71
  • 156
Dean Hiller
  • 19,235
  • 25
  • 129
  • 212

3 Answers3

30

SetWinEventHook() is probably your best bet; you can listen to either the EVENT_SYSTEM_FOREGROUND to listen for foreground window changes - or even EVENT_OBJECT_FOCUS to listen for more fine-grain focus changes within apps and within controls.

You'll need to use this with the WINEVENT_OUTOFCONTEXT flag; this means that the change notification will be delivered asynchronously to your own app, so you won't need a separate DLL - you'll still need to P/Invoke though. But the notification won't be instant - there may be a small delay - but that's implied with asynchronous. If you want to do something absolutely immediately with no delay whatsoever, you're going to need to use C++ and an in-process hook (either SetWinEventHook with WINEVENT_INCONTEXT or the SetSetWindowsHookEx-style hook.)

Here's a sample that seems to do what you're looking for:

using System;
using System.Windows;
using System.Windows.Forms;
using System.Runtime.InteropServices;

class ForegroundTracker
{
            // Delegate and imports from pinvoke.net:

    delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType,
        IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);

    [DllImport("user32.dll")]
    static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr
       hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess,
       uint idThread, uint dwFlags);

    [DllImport("user32.dll")]
    static extern bool UnhookWinEvent(IntPtr hWinEventHook);

            // Constants from winuser.h
    const uint EVENT_SYSTEM_FOREGROUND = 3;
    const uint WINEVENT_OUTOFCONTEXT = 0;

    // Need to ensure delegate is not collected while we're using it,
    // storing it in a class field is simplest way to do this.
    static WinEventDelegate procDelegate = new WinEventDelegate(WinEventProc);

    public static void Main()
    {
        // Listen for foreground changes across all processes/threads on current desktop...
        IntPtr hhook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero,
                procDelegate, 0, 0, WINEVENT_OUTOFCONTEXT);

        // MessageBox provides the necessary mesage loop that SetWinEventHook requires.
        MessageBox.Show("Tracking focus, close message box to exit.");

        UnhookWinEvent(hhook);
    }

    static void WinEventProc(IntPtr hWinEventHook, uint eventType,
        IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
    {
        Console.WriteLine("Foreground changed to {0:x8}", hwnd.ToInt32()); 
    }
}
BrendanMcK
  • 14,252
  • 45
  • 54
  • This was perfect. Converted to VB for my project, and worked like a charm to update the name in an RDS window. – Dave Johnson Oct 04 '18 at 16:47
  • just a note on the message loop - you don't have to use the message box. you can also just call `Application.Run()`, and if you change display settings, you might need to unhook, setup a new delegate, and re-hook (I needed to, when I changed primary monitor) – Nben Aug 29 '23 at 05:11
16

The example above works like a champ. I refactored it a little to make a class that might be useful. I didn't define all the constants, so you'd need to add a few if you want to trap other events.

using System;
using System.Runtime.InteropServices;

#pragma warning disable 1591
// ReSharper disable InconsistentNaming

namespace MosaiqPerformanceMonitor {
     public class EventHook {
          public delegate void WinEventDelegate(
                IntPtr hWinEventHook, uint eventType,
                IntPtr hWnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);

          [DllImport("user32.dll")]
          public static extern IntPtr SetWinEventHook(
                uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc,
                uint idProcess, uint idThread, uint dwFlags);

          [DllImport("user32.dll")]
          public static extern bool UnhookWinEvent(IntPtr hWinEventHook);

          public const uint EVENT_SYSTEM_FOREGROUND = 3;
          public const uint WINEVENT_OUTOFCONTEXT = 0;
          public const uint EVENT_OBJECT_CREATE = 0x8000;

          readonly WinEventDelegate _procDelegate;
          readonly IntPtr _hWinEventHook;

          public EventHook(WinEventDelegate handler, uint eventMin, uint eventMax) {
                _procDelegate = handler;
                _hWinEventHook = SetWinEventHook(eventMin, eventMax, IntPtr.Zero, handler, 0, 0, WINEVENT_OUTOFCONTEXT);
          }

          public EventHook(WinEventDelegate handler, uint eventMin)
                : this(handler, eventMin, eventMin) {
          }

          public void Stop() {
                UnhookWinEvent(_hWinEventHook);
          }

          // Usage Example for EVENT_OBJECT_CREATE (http://msdn.microsoft.com/en-us/library/windows/desktop/dd318066%28v=vs.85%29.aspx)
          // var _objectCreateHook = new EventHook(OnObjectCreate, EventHook.EVENT_OBJECT_CREATE);
          // ...
          // static void OnObjectCreate(IntPtr hWinEventHook, uint eventType, IntPtr hWnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) {
          //    if (!Win32.GetClassName(hWnd).StartsWith("ClassICareAbout"))
          //        return;
          // Note - in Console program, doesn't fire if you have a Console.ReadLine active, so use a Form
     }
}
Wade Hatler
  • 1,785
  • 20
  • 18
  • I downvoted it because I have to research a lot in order to learn how to use the constructor. And the very incomplete pseudo-code at the end doesn't help. Also, it needs forms. Very hacky. – Chris Vilches Jun 16 '17 at 23:09
3

You could install a Windows hook (requires some P/Invoke) and watch for messages sent to the windows. This question lists the messages involved in bringing a window to the foreground. Here is the MSDN documentation for installing a hook

Community
  • 1
  • 1
driis
  • 161,458
  • 45
  • 265
  • 341