Introduction
In the career of almost any programmer, there comes the time when he faces the following problem:
His application is in need of some sort of shiny feature that requires him to get notified when a new window gets opened or closed.
Back in the days of C++, this was rather simple and straightforward. Just register your application as a hook for WM_DESTROY
and WM_CREATE
.
Unfortunately, this has changed with the introduction of .NET and managed applications and things have become more difficult to implement.
In this article, we will discuss an implementation, that allows you to register for events that signal you when a new window has been created or an old window was destroyed.
Background
I am currently writing an application that performs operations on other windows and was searching for an easy way to get a callback whenever a new window is created. I am doing this in C#.
After literally days of searching the internet, I realized that there is a way implemented in .NET to get such a callback.
There is a way in the Win32 API via the calling the method SetWindowsHookEx
. However, for this to work with all windows (and not just windows, that are created within your AppDomain
), you would need to register a so called "global hook", which is not possible in .NET.
For a global hook to work, you would need to inject a DLL into other processes. This is not possible with managed DLLs!
Therefore I came up with a solution that does nearly the same as SetWindowsHookEx
but is limited to only send events when a window is created or destroyed.
For more background information, see this link.
Using the Code
My solution is contained within a single file and consists of two classes:
public class WindowHookEventArgs
{
public IntPtr Handle = IntPtr.Zero;
public string WindowTitle = null;
public string WindowClass = null;
public override string ToString()
{
return "[WindowHookEventArgs|Title:" + WindowTitle + "|Class:"
+ WindowClass + "|Handle:" + Handle + "]";
}
}
This class just contains the information about the raised event and provides you with the window handle (most commonly referred to as hWnd
in C++).
The other class is WindowHookNet
and is a singleton. I chose to implement it as a singleton to avoid performance problems when a dozen or more instances of this class keep pulling for all open windows.
First of all, you have two delegates:
public delegate void WindowHookDelegate(object aSender, WindowHookEventArgs aArgs);
private delegate bool EnumDelegate(IntPtr hWnd, int lParam);
WindowHookDelegate
is used to register for the events raised by WindowHookNet
.
EnumDelegate
is used internally to retrieve a list of all windows open at the moment.
Next there are four events. Two private
and two public
events:
private event WindowHookDelegate InnerWindowCreated;
private event WindowHookDelegate InnerWindowDestroyed;
public event WindowHookDelegate WindowCreated
{
add
{
InnerWindowCreated += value;
if (!iRun)
{
startThread();
}
}
remove
{
InnerWindowCreated -= value;
if (null == InnerWindowCreated &&
null == InnerWindowDestroyed)
iRun = false;
}
}
public event WindowHookDelegate WindowDestroyed
{
add
{
InnerWindowDestroyed += value;
if (!iRun)
{
startThread();
}
}
remove
{
InnerWindowDestroyed -= value;
if (null == InnerWindowCreated &&
null == InnerWindowDestroyed)
iRun = false;
}
}
private void onWindowCreated(WindowHookEventArgs aArgs)
{
if (null != InnerWindowCreated)
InnerWindowCreated(this, aArgs);
}
private void onWindowDestroyed(WindowHookEventArgs aArgs)
{
if (null != InnerWindowDestroyed)
InnerWindowDestroyed(this, aArgs);
}
#endregion
The InnerWindowCreated, InnerWindowDestroyed
events are raised when a new window is created or destroyed. The public events (WindowCreated, WindowDestroyed
) attach new delegates to the inner events and start and stop the thread depending on whether or not there are still registered listeners for the corresponding inner event. onWindowCreated, onWindowDestroyed
are used to prevent NullReferenceException
.
After that, there are some PInvoke / DLLImports:
DllImport("user32.dll", EntryPoint = "EnumDesktopWindows",
ExactSpelling = false, CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool _EnumDesktopWindows(IntPtr hDesktop,
EnumDelegate lpEnumCallbackFunction, IntPtr lParam);
DllImport("user32.dll", EntryPoint = "GetWindowText",
ExactSpelling = false, CharSet = CharSet.Auto, SetLastError = true)]
private static extern int _GetWindowText(IntPtr hWnd,
StringBuilder lpWindowText, int nMaxCount);
[DllImport("user32.dll", EntryPoint = "GetClassName", ExactSpelling = false,
CharSet = CharSet.Auto, SetLastError = true)]
private static extern int _GetClassName(IntPtr hwnd, StringBuilder lpClassName,
int nMaxCount);
EnumDesktopWindows
is used to get the current list of all opened windows.
GetWindowText
is used to get a window's title and GetClassName
is used to get a window's class.
private void run()
This basically just polls for new windows every 500ms and handles termination and startup of WindowHookNet
.
fireCreatedWindows
scans the iNewWindowList
for entries that are not in the iOldWindowList
and therefore represent new windows. If such an entry is found, it is put into a special list and after the scan is completed, all "new window events" are fired.
private void fireCreatedWindows()
{
iEventsToFire.Clear();
foreach (IntPtr tPtr in iNewWindowList.Keys)
{
if (!iOldWindowList.ContainsKey(tPtr))
{
iEventsToFire.Add(iNewWindowList[tPtr]);
}
}
foreach (WindowHookEventArgs tArg in iEventsToFire)
{
iOldWindowList.Add(tArg.Handle, tArg);
onWindowCreated(tArg);
}
}
Exactly the same happens in fireClosedWindows
but the other way round (the iOldWindowList
is scanned for entries that are no longer in the iNewWindowList
).
EnumWindowsProc
is the callback method for EnumDelegate
and is called for every open window. It retrieves the window's class and the window's title and creates the WindowHookEventArgs
object.
History
- Added a whole lot of information about the way this class works and why I created it
I once upon a time was a software developer and developed Java applications for a small enterprise in my home town.
Nowadays I no longer work as a software developer but instead keep on coding for fun.