In komplexen Anwendungen ist es manchmal notwendig herauszufinden, wann eine MessageBox erscheint oder ein neues Fenster im aktuellen Prozess erzeugt wird. Oder gar, wann in einem anderen Prozess ein Fenster geöffnet wurde.
Das .Net Framework bringt von Hause aus leider kein solches Event mit, daher müssen wir hier etwas basteln. Die Windows API hat hier alles, was wir brauchen (nähere Infos zu unmanaged APIs unter
http://www.pinvoke.net ) Um genau zu sein sind die Funktionen, die wir benötigen in der
user32.dll.
EnumDesktopWindows
static private delegate bool EnumWindowsDelegate(IntPtr hWnd, int lParam);
[DllImport("user32.dll")]
static extern bool EnumDesktopWindows(IntPtr hDesktop, EnumWindowsDelegate lpfn, IntPtr lParam);
Diese Funktion iteriert durch alle Fenster, die dem Fenster mit dem handle
hDesktop untergeordnet sind. Für jede Iteration wird der
delegate lpfn aufgerufen. Dadurch können wir uns zu jedem Zeitpunkt eine Liste mit den aktuell offenen WindowHandles aufbauen:
static List<intptr> _currentWindows = new List<intptr>();
IntPtr hwndDesktop = IntPtr.Zero; // current desktop
bool success = EnumDesktopWindows(hwndDesktop,
new EnumWindowsDelegate(
(hWnd, lparam) =>
{
currentWindows.Add(hWnd);
return true;
}), IntPtr.Zero);
GetWindowThreadProcessId
Da wir nicht zwingend alle neu erstellten Fenster melden möchten, sondern wahlweise auch nur die Fenster, die zum aktuellen Prozess gehören, wird die ProzessID des WindowHandle benötigt. Das können wir folgendermaßen auflösen:
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
// get the process id of a window handle
uint lpdwProcessId;
GetWindowThreadProcessId(winHndl, out lpdwProcessId);
GetWindowText
Zu guter Letzt sollten wir neben dem WindowHandle vielleicht noch den Text des Fensters herausfinden, denn mit dem Handle kann man vermutlich im verwalteten Code nicht viel anfangen.
[DllImport("user32.dll")]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpWindowText, int nMaxCount);
// get the window title
StringBuilder windowName = new StringBuilder(256);
GetWindowText(hWnd, windowName, windowName.Capacity);
Alle Drei zusammen
Der Ablauf des Programms sollte nun eigentlich relativ klar sein (hier ein kurzer Abschnitt des essentiellen Ablaufs als Pseudocode):
erzeuge neuen Thread mit folgendem Code:
Liste bekannteFenster := leere Liste
Liste aktuelleFenster := leere Liste
do
- aktuelleFenster <- EnumDesktopWindows
- für alle f in aktuelleFenster , aber nicht in bekannteFenster
- GetWindowThreadProcessId ?= CurrentProcess.Id
- -> Raise WindowCreatedEvent(f, GetWindowText(f))
while (true)
Und für alle, die es gleich mal ausprobieren wollen, hier mein Implementierungsvorschlag:
static class ChildWindowOpenHook
{
#region private fields / delegates
private delegate bool EnumWindowsDelegate(IntPtr hWnd, int lParam);
static private Thread _workerThread = null;
static List _currentWindows = new List();
static List _knownWindows = new List();
static bool _isRunning = true;
static bool _onlyOwnWindows = false;
#endregion
#region DLLImport
[DllImport("user32.dll", SetLastError = true)]
static extern bool EnumDesktopWindows(IntPtr hDesktop, EnumWindowsDelegate lpfn, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("user32.dll")]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpWindowText, int nMaxCount);
#endregion
#region Starter
///
/// C'tor
/// starts the enumeration thread
///
public static void Start(bool onlyOwnWindows)
{
_onlyOwnWindows = onlyOwnWindows;
// 1. start enumerating the first time
EnumerateWindows();
// 2. copy those current windows in out old window list
_knownWindows.AddRange(_currentWindows);
// 3. start the enumeration thread, which will enumerate through the
// current windows periodically
ThreadStart tStart = new ThreadStart(ThreadRunner);
_workerThread = new Thread(tStart);
_workerThread.Name = "window watcher thread";
_workerThread.Priority = ThreadPriority.BelowNormal;
_workerThread.IsBackground = true; // important, so the mainthread will not wait on this one
_workerThread.Start();
}
#endregion
#region ThreadRunner
///
/// Thread Runner
/// - iterates through all desktop windows
/// - fires new window event, when a new window has been detected
///
static private void ThreadRunner()
{
try
{
while (_isRunning)
{
// clear the current window list
_currentWindows.Clear();
// populate the currentwindow list
EnumerateWindows();
// find out which windows are not known already
foreach (IntPtr winHndl in _currentWindows.Where(hndl => !_knownWindows.Contains(hndl)))
{
// add the windowhandle of a "NEW WINDOW" to our list of known windows
_knownWindows.Add(winHndl);
// get the process id of that new window
uint lpdwProcessId;
GetWindowThreadProcessId(winHndl, out lpdwProcessId);
// find out if its on the same process, as we are
if (
!_onlyOwnWindows
|| (uint)System.Diagnostics.Process.GetCurrentProcess().Id == lpdwProcessId)
{
// raise the event
OnWindowCreated(winHndl);
}
}
Thread.Sleep(500);
}
}
catch (Exception aException)
{
// write down the exeption that occured
Console.WriteLine("exception in thread:" + aException);
}
}
#endregion
#region EnumerateWindows
static private void EnumerateWindows()
{
IntPtr hwndDesktop = IntPtr.Zero; // current desktop
bool success = EnumDesktopWindows(hwndDesktop,
new EnumWindowsDelegate(
(hWnd, lparam) =>
{
_currentWindows.Add(hWnd);
return true;
}), IntPtr.Zero);
// if we have no success, lets throw an error
if (!success)
{
// get the last win32 error code
int errorCode = Marshal.GetLastWin32Error();
string errorMessage = string.Format("EnumDesktopWindows failed, error code: {0}.", errorCode);
throw new Exception(errorMessage);
}
}
#endregion
#region Stopper
///
/// Stops the ChildWindowOpenHook
///
public static void Stop()
{
_isRunning = false;
_workerThread.Abort();
}
#endregion
#region Event Handling
///
/// delegate for WindowCreated event
///
/// name of the window, that has been created
/// window handle
public delegate void WindowCreatedEventHandler(string windowName, IntPtr hWnd);
///
/// handles window created events
///
public static event WindowCreatedEventHandler WindowCreated;
///
/// being called, when a window has been created
///
/// the window handle
private static void OnWindowCreated(IntPtr hWnd)
{
if (WindowCreated!=null && _isRunning)
{
// get the window title
StringBuilder windowName = new StringBuilder(256);
GetWindowText(hWnd, windowName, windowName.Capacity);
// handle the event
WindowCreated(windowName.ToString(), hWnd);
}
}
#endregion
}
Man kann sich folgendermaßen an das event hängen:
public MainWindow()
{
InitializeComponent();
ChildWindowOpenHook.Start(true);
ChildWindowOpenHook.WindowCreated += ChildWindowOpenHook_WindowCreated;
}
void ChildWindowOpenHook_WindowCreated(string windowName, IntPtr hWnd)
{
Console.WriteLine(windowName);
}