Detect active window changed using C# without polling

24,985

Solution 1

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";
    } 

Solution 2

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!

Solution 3

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

Share:
24,985
Joe
Author by

Joe

Updated on December 05, 2020

Comments

  • Joe
    Joe over 3 years

    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

  • Ehsan Zargar Ershadi
    Ehsan Zargar Ershadi about 12 years
    Can you give some examples , please.
  • Raymond Chen
    Raymond Chen about 12 years
  • Kenneth K.
    Kenneth K. about 11 years
    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 for more info.
  • Anthony Raimondo
    Anthony Raimondo almost 9 years
    This doesnt seem to work with a console application, does anyone know why? I removed InitializeComponent().
  • public wireless
    public wireless almost 9 years
    Additional info: Your out of context function must be fast. (msdn.microsoft.com/en-us/library/windows/desktop/…)
  • public wireless
    public wireless almost 9 years
    @AnthonyRaimondo Apparently it 'doesn't fire if you have a Console.ReadLine active'. See stackoverflow.com/questions/8840926/…
  • Wobbles
    Wobbles over 7 years
    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.
  • NtFreX
    NtFreX almost 7 years
    You should also call UnhookWinEvent in the end.
  • IneedHelp
    IneedHelp over 5 years
    Too bad this doesn't work properly when resorting windows (from the minimized state)..
  • Walter Stabosz
    Walter Stabosz over 2 years
    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.
  • A Petrov
    A Petrov over 2 years
    This does not work in a windows service.