Labels

Linux (6) OpenCV (4) MATLAB (3) Mac OS X (3) Windows (2) C# (1) Node JS (1)

2014年8月29日 星期五

Simple Windows UI Automation (Controlling other windows or processes ) by Using AutoIt or C#

Although few people are talking Windows programming nowadays, Windows Automation is still useful. I currently studied several ways to do Windows automation for factory automation or controlling POS (Point-of-Sales). All methods are calling Windows APIs (e.g. FindWindow & SendMessage), but using different wrapper.

The simplest way is to use AutoIt, which is a great wrapper for Windows API. It also has a built-in Window Info Tool:

With a few lines you can write an automation script. For example, we call a notepad window, wait for its show-up, send text to it and then close it without saving:
Run("notepad.exe")
WinWaitActive("Untitled - Notepad")
Send("This is some text.")
WinClose("Untitled - Notepad")
WinWaitActive("Notepad", "Do you want to save");
Send("!n")  //Alt-n to quit notepad without saving 


Although we can totally build an application by using AutoIt, most of the time we still want to control other Windows through our own program. Here is an example in C#.

IntPtr hWnd = IntPtr.Zero;
foreach (Process pList in Process.GetProcesses())
{
    if (pList.MainWindowTitle.Contains(wName))
        hWnd = pList.MainWindowHandle;
}
return hWnd; //Note: may be zero if can't find a matched title

Or we can call Windows API directly in C#. For wait for Window Active, we can use a timer to find window periodically:
[DllImport("user32.dll", EntryPoint = "FindWindowEx")]
    public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("User32.dll")]
    public static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);
    
private void timer1_tick(object sender, EventArgs e)
{
    Process [] notepads = Process.GetProcessesByName("notepad");
    if(notepads.Length==0) return; else timer1.enabled=false;           
    if (notepads[0] != null)
    {
        IntPtr child = FindWindowEx(notepads[0].MainWindowHandle, new IntPtr(0), "Edit", null);
        SendMessage(child, 0x000C, 0, textBox1.Text);
    }
}
For program like Notepad, it's better to find process first, because the title of window is changed by it's content.

There is a great Windows message APIs wrapper for using WM_DATACOPY called MessageHelper :
https://gist.github.com/BoyCook/5075907

More advanced UI automation tricks can be found in:
UI Automation Clients for Managed Code

One important application of Windows Automation is to send inputs (keys or mouse clicks) to background Windows. However, I didn't found an effective way to do this task. SendMessage or PeekMessage not always work. The current method I am using is to set my application on top, call "SetForegroundWindow" and "SendKeys". For mouse clicks, I'm still looking for effective method.

// import the function in your class
[DllImport ("User32.dll")]
static extern int SetForegroundWindow(IntPtr point);

// Find the target process and send keys

Process p = Process.GetProcessesByName("notepad").FirstOrDefault();
if (p != null)
{
    IntPtr h = p.MainWindowHandle;
    SetForegroundWindow(h);
    SendKeys.Send("{F1}");
    SendKeys.SendWait("{Enter}");
}

沒有留言:

張貼留言