Mark S. Rasmussen improve.dk
Apr 07
2007

Last time I made an example of how to enumerate windows. This time I present to you a class that greatly simplifies the process of searching for specific windows, types of windows, windows belonging to a specific process, having a specific text. You can search for any number of these parameters at the same time, using regular expressions for all string matches to provide optimal flexibility.

using System.Runtime.InteropServices;
using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Diagnostics;

namespace Searching_for_windows
{
	class Program
	{
		// Win32 constants.
		const int WM_GETTEXT = 0x000D;
		const int WM_GETTEXTLENGTH = 0x000E;

		// Win32 functions that have all been used in previous blogs.
		[DllImport("User32.Dll")]
		private static extern void GetClassName(int hWnd, StringBuilder s, int nMaxCount);

		[DllImport("User32.dll")]
		private static extern int GetWindowText(int hWnd, StringBuilder text, int count);

		[DllImport("User32.dll")]
		private static extern Int32 SendMessage(int hWnd, int Msg, int wParam, StringBuilder lParam);

		[DllImport("User32.dll")]
		private static extern Int32 SendMessage(int hWnd, int Msg, int wParam, int lParam);

		// Main entrypoint function
		static void Main(string[] args)
		{
			WindowFinder wf = new WindowFinder();

			// Find all Internet Explorer instances
			wf.FindWindows(0, null, null, new Regex("iexplore"), new WindowFinder.FoundWindowCallback(foundWindow));

			// Find all visual studio instances
			wf.FindWindows(0, null, new Regex(" - Microsoft Visual Studio"), new Regex("devenv"), new WindowFinder.FoundWindowCallback(foundWindow));

			Console.WriteLine("Done");
			Console.Read();
		}

		// Gets called each time a window is found by the WindowFinder class.
		private static bool foundWindow(int handle)
		{
			// Print the window info.
			printWindowInfo(handle);

			// Continue on with next window.
			return true;
		}

		// Prints basic properties of a window, uses function already used in previous blogs.
		private static void printWindowInfo(int handle)
		{
			// Get the class.
			StringBuilder sbClass = new StringBuilder(256);
			GetClassName(handle, sbClass, sbClass.Capacity);

			// Get the text.
			int txtLength = SendMessage(handle, WM_GETTEXTLENGTH, 0, 0);
			StringBuilder sbText = new StringBuilder(txtLength + 1);
			SendMessage(handle, WM_GETTEXT, sbText.Capacity, sbText);

			// Now we can write out the information we have on the window.
			Console.WriteLine("Handle: " + handle);
			Console.WriteLine("Class : " + sbClass);
			Console.WriteLine("Text  : " + sbText);
			Console.WriteLine();
		}
	}

	/// <summary>
	/// A class used for finding windows based upon their class, title, process and parent window handle.
	/// </summary>
	public class WindowFinder
	{
		// Win32 constants.
		const int WM_GETTEXT = 0x000D;
		const int WM_GETTEXTLENGTH = 0x000E;

		// Win32 functions that have all been used in previous blogs.
		[DllImport("User32.Dll")]
		private static extern void GetClassName(int hWnd, StringBuilder s, int nMaxCount);

		[DllImport("User32.dll")]
		private static extern int GetWindowText(int hWnd, StringBuilder text, int count);

		[DllImport("User32.dll")]
		private static extern Int32 SendMessage(int hWnd, int Msg, int wParam, StringBuilder lParam);

		[DllImport("User32.dll")]
		private static extern Int32 SendMessage(int hWnd, int Msg, int wParam, int lParam);

		[DllImport("user32")]
		private static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);

		// EnumChildWindows works just like EnumWindows, except we can provide a parameter that specifies the parent
		// window handle. If this is NULL or zero, it works just like EnumWindows. Otherwise it'll only return windows
		// whose parent window handle matches the hWndParent parameter.
		[DllImport("user32.Dll")]
		private static extern Boolean EnumChildWindows(int hWndParent, PChildCallBack lpEnumFunc, int lParam);

		// The PChildCallBack delegate that we used with EnumWindows.
		private delegate bool PChildCallBack(int hWnd, int lParam);

		// This is an event that is run each time a window was found that matches the search criterias. The boolean
		// return value of the delegate matches the functionality of the PChildCallBack delegate function.
		private event FoundWindowCallback foundWindow;
		public delegate bool FoundWindowCallback(int hWnd);

		// Members that'll hold the search criterias while searching.
		private int parentHandle;
		private Regex className;
		private Regex windowText;
		private Regex process;

		// The main search function of the WindowFinder class. The parentHandle parameter is optional, taking in a zero if omitted.
		// The className can be null as well, in this case the class name will not be searched. For the window text we can input
		// a Regex object that will be matched to the window text, unless it's null. The process parameter can be null as well,
		// otherwise it'll match on the process name (Internet Explorer = "iexplore"). Finally we take the FoundWindowCallback
		// function that'll be called each time a suitable window has been found.
		public void FindWindows(int parentHandle, Regex className, Regex windowText, Regex process, FoundWindowCallback fwc)
		{
			this.parentHandle = parentHandle;
			this.className = className;
			this.windowText = windowText;
			this.process = process;

			// Add the FounWindowCallback to the foundWindow event.
			foundWindow = fwc;

			// Invoke the EnumChildWindows function.
			EnumChildWindows(parentHandle, new PChildCallBack(enumChildWindowsCallback), 0);
		}

		// This function gets called each time a window is found by the EnumChildWindows function. The foun windows here
		// are NOT the final found windows as the only filtering done by EnumChildWindows is on the parent window handle.
		private bool enumChildWindowsCallback(int handle, int lParam)
		{
			// If a class name was provided, check to see if it matches the window.
			if (className != null)
			{
				StringBuilder sbClass = new StringBuilder(256);
				GetClassName(handle, sbClass, sbClass.Capacity);

				// If it does not match, return true so we can continue on with the next window.
				if (!className.IsMatch(sbClass.ToString()))
					return true;
			}

			// If a window text was provided, check to see if it matches the window.
			if (windowText != null)
			{
				int txtLength = SendMessage(handle, WM_GETTEXTLENGTH, 0, 0);
				StringBuilder sbText = new StringBuilder(txtLength + 1);
				SendMessage(handle, WM_GETTEXT, sbText.Capacity, sbText);

				// If it does not match, return true so we can continue on with the next window.
				if (!windowText.IsMatch(sbText.ToString()))
					return true;
			}

			// If a process name was provided, check to see if it matches the window.
			if (process != null)
			{
				int processID;
				GetWindowThreadProcessId(handle, out processID);

				// Now that we have the process ID, we can use the built in .NET function to obtain a process object.
				Process p = Process.GetProcessById(processID);

				// If it does not match, return true so we can continue on with the next window.
				if (!process.IsMatch(p.ProcessName))
					return true;
			}

			// If we get to this point, the window is a match. Now invoke the foundWindow event and based upon
			// the return value, whether we should continue to search for windows.
			return foundWindow(handle);
		}
	}
}

And the result:

Mark S. Rasmussen
I'm the CTO at iPaper where I cuddle with databases, mold code and maintain the overall technical & team responsibility. I'm an avid speaker at user groups & conferences. I love life, motorcycles, photography and all things technical. Say hi on Twitter, write me an email or look me up on LinkedIn.