Thursday, January 10, 2013 9:16 PM
Win32 API offers a lot of functionality for Windows platform, which the .NET Framework doesn’t have for good reason – One of them being ‘managed’. However, that doesn’t stop us from doing some unsafe things! We can always build a wrapper for Win32 API and then call those functions directly from .NET.
Let us evaluate one of such area which requires calling Win32 API functions from .NET. That area is ‘Simulating UI Automation’. Of course, the subject is too vast to fit in a single blog post, however, we can start with some basic things at least!
Our objective for this exercise is : “Launch an instance of Notepad and write ‘hello’ in it”; (Sorry for the semicolon, it has become a habit
)
So let us start with creating a C# Console application. Next step is to create a new class called Win32 (you can name it whatever you like) and declare a couple of ‘static extern’ methods whose signature should match that of the unmanaged native Win32 API methods. There are a couple of DLLs for core Win32 APIs. You can categorize them into 8 areas. For more info, please visit http://en.wikipedia.org/wiki/Windows_API. We will be focusing on User32.dll for today’s topic as it deals with the ‘User Interface’.
/// <summary>
/// Win32 wrapper for User32.dll functions
/// </summary>
public class Win32
{
public const int WM_KEYDOWN = 0x100;
public const int WM_KEYUP = 0x101;
[DllImport("User32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(String lpClassName, String lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("User32.dll", SetLastError = true)]
public static extern IntPtr SetForegroundWindow(IntPtr hWnd);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
public static extern bool PostMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);
}
|
Note: Make sure you have added the following namespace.
using System.Runtime.InteropServices;
The FindWindow method is used to find a handle to any window by its application class name and its title. You can pass null as the first parameter.
The FindWindowEx method finds child windows.
The SetForegroundWindow sets focus on the specified window.
The PostMessage is used to send key strokes to the window.
Now, although not necessary, I have created another wrapper around this Win32 wrapper which exposes more user friendly set of methods. To do so, I have created another class named UIAutomationHelper.
/// <summary>
/// This class helps in UI Automation
/// </summary>
public class UIAutomationHelper
{
/// <summary>
/// Find a window specified by the window title
/// </summary>
/// <param name="windowName"></param>
/// <returns></returns>
public static IntPtr FindWindow(string windowName)
{
return Win32.FindWindow(null, windowName);
}
/// <summary>
/// Find a window specified by the class name as well as the window title
/// </summary>
/// <param name="className"></param>
/// <param name="windowName"></param>
/// <returns></returns>
public static IntPtr FindWindow(string className, string windowName)
{
return Win32.FindWindow(className, windowName);
}
/// <summary>
/// Finds a window specified by the window title and set focues on that window
/// </summary>
/// <param name="windowName"></param>
/// <returns></returns>
public static IntPtr FindWindowAndFocus(string windowName)
{
return UIAutomationHelper.FindWindowAndFocus(null, windowName);
}
/// <summary>
/// Finds a window specified by the class name and window title and set focuses on that window
/// </summary>
/// <param name="className"></param>
/// <param name="windowName"></param>
/// <returns></returns>
public static IntPtr FindWindowAndFocus(string className, string windowName)
{
IntPtr hWindow = Win32.FindWindow(className, windowName);
Win32.SetForegroundWindow(hWindow);
return hWindow;
}
/// <summary>
/// Finds a child window
/// </summary>
/// <param name="windowName"></param>
/// <param name="childWindowName"></param>
/// <returns></returns>
public static IntPtr FindChildWindow(String windowName, String childWindowName)
{
return UIAutomationHelper.FindChildWindow(null, windowName, null, childWindowName);
}
/// <summary>
/// Finds a child window
/// </summary>
/// <param name="className"></param>
/// <param name="windowName"></param>
/// <param name="childClassName"></param>
/// <param name="childWindowName"></param>
/// <returns></returns>
public static IntPtr FindChildWindow(String className, String windowName, String childClassName, String childWindowName)
{
IntPtr hWindow = Win32.FindWindow(className, windowName);
IntPtr hWindowEx = Win32.FindWindowEx(hWindow, IntPtr.Zero, childClassName, childWindowName);
return hWindowEx;
}
/// <summary>
/// Simulates pressing a key
/// </summary>
/// <param name="hWindow"></param>
/// <param name="key"></param>
public static void PressKey(IntPtr hWindow, System.Windows.Input.Key key)
{
Win32.PostMessage(hWindow, Win32.WM_KEYDOWN, System.Windows.Input.KeyInterop.VirtualKeyFromKey(key), 0);
}
/// <summary>
/// Simulates pressing several keys
/// </summary>
/// <param name="hWindow"></param>
/// <param name="keys"></param>
public static void PressKeys(IntPtr hWindow, IEnumerable<System.Windows.Input.Key> keys)
{
foreach (var key in keys)
{
UIAutomationHelper.PressKey(hWindow, key);
}
}
}
|
Now, let us create our main program.
static void Main(string[] args)
{
Console.WriteLine("Starting Automation ...");
//Starting Notepad
ProcessStartInfo notepadStartInfo = new ProcessStartInfo(@"C:\Windows\System32\notepad.exe");
Process.Start(notepadStartInfo);
//Wait for 2 seconds
Thread.Sleep(2000);
//Get handle and simulate key press to type Hello
IntPtr handle = UIAutomationHelper.FindChildWindow(null, "Untitled - Notepad", "edit", null);
UIAutomationHelper.PressKeys( handle, new[] { Key.H, Key.E, Key.L, Key.L, Key.O, Key.Enter});
Console.WriteLine("Stopping Automation ...");
Console.WriteLine("Press any key to terminate ...");
Console.ReadKey();
}
|
Note: Make sure you have added the following namespaces.
using System.Diagnostics;
using System.Threading;
using System.Windows.Input;
You will also need to add reference to WindowsBase.dll
Now, what we are doing here is utilizing the Process and ProcessStartInfo to start an instance of Notepad. Then after waiting for 2 seconds, we are making call to your UIAutomationHelper method called FindChildWindow with parameters as className = null, windowName = “Untitled – Notepad”, childClassName=”edit” and childWindowName=null. Now this method internally calls the Win32.FindWindow and Win32.FindWindowEx and returns a handle of the Notepad instance we want use. The PressKeys method of our helper is also making call to Win32.PostMessage method internally which is used to post key strokes to the Notepad window.
Now, let’s compile and run this program. This is what we get as an output.


So, this is how we go about writing automated text in the any window. Now, if you want to know more about what we can do with our target window (in our case it was notepad), you can use a utility called Inspect.exe which can be found at the following location.
C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\Inspect.exe
This utility is part of Windows SDK that can be downloaded from the following location.
http://www.microsoft.com/en-in/download/details.aspx?id=8279
Best Regards,
Om Talsania