38

How might one invoke a callback whenever the current active window changes. I've seen how it might be done using CBTProc. However, global events aren't easy to hook into with managed code. I'm interested in finding a way that doesn't require polling. I'd prefer an event driven approach.

Regards

BrendanMcK
  • 14,252
  • 45
  • 54
Joe
  • 1,043
  • 2
  • 12
  • 21
  • 2
    According to http://support.microsoft.com/kb/318804, you can't do global hooks in .NET, and since global hooks are how you monitor these types of events, it looks like your only other options are polling or writing some unmanaged code (in C++, for example) that provides an interface that you can access from C#. – Jim Mischel Dec 07 '10 at 00:23
  • Jim I think you have the right answer. – Joe Dec 08 '10 at 01:02
  • 3
    Jim is actually only partly right. He must've skimmed that article. Keyboard and Mouse hooks are global (if you read to the end you'd see that) and you can do those from C#, you can't however, do what you're trying to do from C#. – Ben Lesh Jan 27 '11 at 16:48

3 Answers3

71

Create a new windows forms project, add a textbox, make it multiline, and set the textbox Dock property to fill, name it Log and paste in the following code (you'll need to add System.Runtime.InteropServices to your usings)...

    WinEventDelegate dele = null;

    public Form1()
    {
        InitializeComponent();
        dele = new WinEventDelegate(WinEventProc);
        IntPtr m_hhook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, dele, 0, 0, WINEVENT_OUTOFCONTEXT);
    }

    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);

    private const uint WINEVENT_OUTOFCONTEXT = 0;
    private const uint EVENT_SYSTEM_FOREGROUND = 3;

    [DllImport("user32.dll")]
    static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll")]
    static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);

    private string GetActiveWindowTitle()
    {
        const int nChars = 256;
        IntPtr handle = IntPtr.Zero;
        StringBuilder Buff = new StringBuilder(nChars);
        handle = GetForegroundWindow();

        if (GetWindowText(handle, Buff, nChars) > 0)
        {
            return Buff.ToString();
        }
        return null;
    }

    public void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
    {
        Log.Text += GetActiveWindowTitle() + "\r\n";
    } 
Idle_Mind
  • 38,363
  • 3
  • 29
  • 40
Stephen Lee Parker
  • 1,215
  • 9
  • 19
  • 3
    As per Savvas Sopiadis' answer, the scope of `dele` needs to be widened to prevent the delegate from being disposed. See the comments on the [documentation page](http://msdn.microsoft.com/en-us/library/windows/desktop/dd373640(v=vs.85).aspx) for more info. – Kenneth K. Feb 27 '13 at 01:48
  • This doesnt seem to work with a console application, does anyone know why? I removed InitializeComponent(). – Anthony Raimondo Jun 27 '15 at 21:55
  • 1
    @AnthonyRaimondo Apparently it 'doesn't fire if you have a Console.ReadLine active'. See http://stackoverflow.com/questions/8840926/asynchronously-getforegroundwindow-via-sendmessage-or-something/11943387#11943387 – public wireless Jul 17 '15 at 20:57
  • To reduce interop calls and clean up code a bit; `GetForegroundWindow()` is not needed as `WinEventProc` already receives the foreground handle as as `hwnd` which can be used to get the title. – Wobbles Feb 08 '17 at 15:12
  • 2
    You should also call [`UnhookWinEvent`](https://msdn.microsoft.com/en-us/library/windows/desktop/dd373671(v=vs.85).aspx) in the end. – NtFreX Jul 16 '17 at 19:12
  • Too bad this doesn't work properly when resorting windows (from the minimized state).. – IneedHelp Sep 21 '18 at 18:51
  • This does not work in a windows service. – A Petrov Jan 16 '22 at 20:56
21

I know this thread is old, but for sake of future use: when running the code you'll notice a crash after a while. This is caused from the line in the Form constructor:

public Form1()
    {
        InitializeComponent();
        WinEventDelegate dele = new WinEventDelegate(WinEventProc);//<-causing ERROR
        IntPtr m_hhook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, dele, 0, 0, WINEVENT_OUTOFCONTEXT);
    }

Instead of the above make the following modification:

public Form1()
        {
            InitializeComponent();
            dele = new WinEventDelegate(WinEventProc); 
            IntPtr m_hhook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, dele, 0, 0, WINEVENT_OUTOFCONTEXT);
        }
WinEventDelegate dele = null;

..works now as expected!

Savvas Sopiadis
  • 8,213
  • 10
  • 34
  • 53
  • It crashes because when `WinEventDelegate dele` is scoped inside of the `Form1` constructor, it is a candidate for garbage collection since the reference is lost at the end of the constructor. Scoping the at the class level will keep the reference alive until the class object is disposed. – Walter Stabosz Jan 10 '22 at 18:08
7

You can use SetWinEventHook and listen for the EVENT_SYSTEM_FOREGROUND event. Use the WINEVENT_OUTOFCONTEXT flag to avoid the global-hook problem.

Raymond Chen
  • 44,448
  • 11
  • 96
  • 135