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))
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); }
Keine Kommentare:
Kommentar veröffentlichen