I’m trying to get alerted as to when an external application is closed. For my test i’ve retrieved Notepad’s hwnd and set it into my monitor. Strangely, i get the system event whenever i click from notepad onto some other application even though notepad hasn’t closed. Does anyone have any idea why?
some quick example calling code:
uint EVENT_OBJECT_DESTROY = 0x8001;
IntPtr notepadHwnd = <your pointer>
var mon = new WindowMonitor(notepadHwnd, EVENT_OBJECT_DESTROY);
mon.EventOccurred += (sender, args) => Console.WriteLine("closed");
The monitor:
public class WindowMonitor : IDisposable
{
//store delegate to prevent GC
private User32.WinEventDelegate dEvent;
private IntPtr _hook;
private readonly IntPtr _window;
private readonly List<uint> _watchedEvents = new List<uint>();
public event EventHandler<AccessibleEventTypeEventArgs> EventOccurred;
public WindowMonitor(IntPtr windowToMonitor, params User32.AccessibleEventType[] eventsToMonitor)
{
//prevent junk
if (eventsToMonitor == null || eventsToMonitor.Length == 0)
throw new ArgumentNullException("eventsToMonitor", "Must specify events to monitor");
if (windowToMonitor == IntPtr.Zero)
throw new ArgumentNullException("windowToMonitor", "Must specify a valid window handle to monitor");
_window = windowToMonitor;
//cast them now so we dont have to cast each one when evaluting events
uint lowest = (uint) User32.AccessibleEventType.EVENT_MAX;
uint highest = (uint) User32.AccessibleEventType.EVENT_MIN;
foreach (User32.AccessibleEventType eventType in eventsToMonitor)
{
var castType = (uint) eventType;
_watchedEvents.Add(castType);
//need the range of events to subscribe to
if (castType > highest)
highest = castType;
if (castType < lowest)
lowest = castType;
}
//sign up for the event
dEvent = this.winEvent;
_hook = User32.SetWinEventHook(lowest, highest, IntPtr.Zero, dEvent, 0, 0, User32.WINEVENT_OUTOFCONTEXT);
//ensure it worked
if (IntPtr.Zero.Equals(_hook)) throw new Win32Exception();
//no kill callback
GC.KeepAlive(dEvent);
}
private void winEvent(IntPtr hWinEventHook, uint eventType, IntPtr hWnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
//only care about the events for the specified window
if (hWnd != _window)
return;
if (_watchedEvents.Contains(eventType))
{
if (EventOccurred != null)
EventOccurred(this, new AccessibleEventTypeEventArgs((User32.AccessibleEventType) eventType));
}
}
public void Dispose()
{
//unhook the listener
if (!IntPtr.Zero.Equals(_hook))
User32.UnhookWinEvent(_hook);
//clear variables
_hook = IntPtr.Zero;
dEvent = null;
//kill any event listeners
EventOccurred = null;
GC.SuppressFinalize(this);
}
}
and some winapi (i omitted the list of event consts):
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);
You’re probably seeing an event for the caret. When you get a winevent, you usually need to check the HWND and the idObject to make sure it’s actually an event about the part of the window you care about; whether the window itself (OBJID_WINDOW) or some other aspect of it, such as scrollbars, caret, or something else.
For debugging winevents, check out the Accessible Event Watcher tool – configure it to listen to WinEvents via the Mode menu, then in Mode/Settings… select the events you want – eg. EVENT_OBJECT_DESTROY, and then the event information to display, usually the hwnd, idObject and idChild.
—
By the way, one comment on your code: if you are only looking for events from a specific window, it’s more efficient to get that window’s thread (GetWindowThreadProcessId), and then use SetWinEventHook with that idThread to only get events from that specific thread instead of passing 0 to listen to all threads; this allows Win32 will do the filtering for you at the event source, which is more efficient than getting events of that type from all threads on the entire desktop and then just ignoring the ones you didn’t need.