7.2 模拟键盘复杂操作
7.2.1 Introduction
键盘的操作相对比较灵活多变,在7.1节中我们通过System.Windows.Forms.SendKeys类中的方法来实现模拟按键。但它是一种模拟常规的敲击键盘,但有时候我们需要按下某个键而不松开,例如按住CTRL键多选操作等。在此种情况下我们需要同调用Win32 API中的keybd_event函数模拟对键盘上的某些键的Down和Up操作。
7.2.2 keybd_event Function
如下代码为模拟键盘操作的Win32 API函数:
/// <summary> /// Simulate the keyboard operations /// </summary> /// <param name="bVk">Virtual key value</param> /// <param name="bScan">Hardware scan code</param> /// <param name="dwFlags">Action flag</param> /// <param name="dwExtraInfo">Extension information which assication the keyborad action</param> [DllImport("user32.dll")] static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo); |
上表中的代码是通过调用Win32 API中的keybd_event函数模拟对键盘上的某些键的Down和Up操作。
其中参数列表对应的解释如下
static extern void keybd_event
(
byte bVk,//虚拟键值
byte bScan,//硬件扫描码
uint dwFlags,//动作标识
uint dwExtraInfo//与键盘动作关联的辅加信息
);
/// <summary> /// Map virtual key /// </summary> /// <param name="uCode">Virtual key code</param> /// <param name="uMapType">Defines the translation to be performed</param> /// <returns></returns> [DllImport("user32.dll")] static extern byte MapVirtualKey(uint uCode, uint uMapType); |
上表中的代码中函数将一虚拟键码翻译(映射)成一扫描码或一字符值,或者将一扫描码翻译成一虚拟键码。
1) uCode:定义一个键的扫描码或虚拟键码。该值如何解释依赖于uMapType参数的值。
2) wMapType:定义将要执行的翻译。该参数的值依赖于uCode参数的值。取值如下:
a) 0:代表uCode是一虚拟键码且被翻译为一扫描码。若一虚拟键码不区分左右,则返回左键的扫描码。若未进行翻译,则函数返回O。
b) 1:代表uCode是一扫描码且被翻译为一虚拟键码,且此虚拟键码不区分左右。若未进行翻译,则函数返回0。
c) 2:代表uCode为一虚拟键码且被翻译为一未被移位的字符值存放于返回值的低序字中。死键(发音符号)则通过设置返回值的最高位来表示。若未进行翻译,则函数返回0。
d) 3:代表uCode为一扫描码且被翻译为区分左右键的一虚拟键码。若未进行翻译,则函数返回0。
返回值可以是一扫描码,或一虚拟键码,或一字符值,这完全依赖于不同的uCode和uMapType的值。若未进行翻译,则函数返回O。
下面我们通过模拟鼠标和键盘相结合的方式来达到多选ListBox中的项:
using System; using System.Text; using System.Diagnostics; using System.Threading; using System.Windows.Automation; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Forms; namespace UIATest { class Program { static void Main(string[] args) { Process process = Process.Start(@"E:\WorkBook\ATP\WpfApp\bin\Debug\WpfApp.exe"); int processId = process.Id; AutomationElement element = FindElementById(processId, "ListBox1"); AutomationElementCollection listItems = element.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ListItem)); SendCtrlKey(true); Thread.Sleep(500); ClickLeftMouse(listItems[0]); Thread.Sleep(500); ClickLeftMouse(listItems[2]); Thread.Sleep(500); ClickLeftMouse(listItems[3]); Thread.Sleep(500); ClickLeftMouse(listItems[5]); SendCtrlKey(false); //Note: The statement 'Thread.Sleep (500)' is to make each of these steps can be seen by tester. Console.WriteLine("Test finised."); } #region Simulate keyboard /// <summary> /// Simulate the keyboard operations /// </summary> /// <param name="bVk">Virtual key value</param> /// <param name="bScan">Hardware scan code</param> /// <param name="dwFlags">Action flag</param> /// <param name="dwExtraInfo">Extension information which assication the keyborad action</param> [DllImport("user32.dll")] static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo); /// <summary> /// Map virtual key /// </summary> /// <param name="uCode">Virtual key code</param> /// <param name="uMap">Defines the translation to be performed</param> /// <returns></returns> [DllImport("user32.dll")] static extern byte MapVirtualKey(uint uCode, uint uMap); public static void SendCtrlKey(bool isKeyDown) { if (!isKeyDown) { keybd_event(17, MapVirtualKey(17, 0), 0x2, 0);//Up CTRL key } else { keybd_event(17, MapVirtualKey(17, 0), 0, 0); //Down CTRL key } } #endregion #region ClickMouse #region Import DLL /// <summary> /// Add mouse move event /// </summary> /// <param name="x">Move to specify x coordinate</param> /// <param name="y">Move to specify y coordinate</param> /// <returns></returns> [DllImport("user32.dll")] extern static bool SetCursorPos(int x, int y); /// <summary> /// Mouse click event /// </summary> /// <param name="mouseEventFlag">MouseEventFlag </param> /// <param name="incrementX">X coordinate</param> /// <param name="incrementY">Y coordinate</param> /// <param name="data"></param> /// <param name="extraInfo"></param> [DllImport("user32.dll")] extern static void mouse_event(int mouseEventFlag, int incrementX, int incrementY, uint data, UIntPtr extraInfo); const int MOUSEEVENTF_MOVE = 0x0001; const int MOUSEEVENTF_LEFTDOWN = 0x0002; const int MOUSEEVENTF_LEFTUP = 0x0004; const int MOUSEEVENTF_RIGHTDOWN = 0x0008; const int MOUSEEVENTF_RIGHTUP = 0x0010; const int MOUSEEVENTF_MIDDLEDOWN = 0x0020; const int MOUSEEVENTF_MIDDLEUP = 0x0040; const int MOUSEEVENTF_ABSOLUTE = 0x8000; #endregion public static void ClickLeftMouse(AutomationElement element) { if (element == null) { throw new NullReferenceException(string.Format("Element with AutomationId '{0}' and Name '{1}' can not be find.", element.Current.AutomationId, element.Current.Name)); } Rect rect = element.Current.BoundingRectangle; int IncrementX = (int)(rect.Left + rect.Width / 2); int IncrementY = (int)(rect.Top + rect.Height / 2); //Make the cursor position to the element. SetCursorPos(IncrementX, IncrementY); //Make the left mouse down and up. mouse_event(MOUSEEVENTF_LEFTDOWN, IncrementX, IncrementY, 0, UIntPtr.Zero); mouse_event(MOUSEEVENTF_LEFTUP, IncrementX, IncrementY, 0, UIntPtr.Zero); } #endregion /// <summary> /// Get the automation elemention of current form. /// </summary> /// <param name="processId">Process Id</param> /// <returns>Target element</returns> public static AutomationElement FindWindowByProcessId(int processId) { AutomationElement targetWindow = null; int count = 0; try { Process p = Process.GetProcessById(processId); targetWindow = AutomationElement.FromHandle(p.MainWindowHandle); return targetWindow; } catch (Exception ex) { count++; StringBuilder sb = new StringBuilder(); string message = sb.AppendLine(string.Format("Target window is not existing.try #{0}", count)).ToString(); if (count > 5) { throw new InvalidProgramException(message, ex); } else { return FindWindowByProcessId(processId); } } } /// <summary> /// Get the automation element by automation Id. /// </summary> /// <param name="windowName">Window name</param> /// <param name="automationId">Control automation Id</param> /// <returns>Automatin element searched by automation Id</returns> public static AutomationElement FindElementById(int processId, string automationId) { AutomationElement aeForm = FindWindowByProcessId(processId); AutomationElement tarFindElement = aeForm.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, automationId)); return tarFindElement; } } } |
对应的XAML代码如下:
<Window x:Class="WpfApp.ListBox" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="ListBox" Height="219" Width="349"> <Grid> <ListBox SelectionMode="Extended" Name="ListBox1"> <ListBoxItem>ListBoxItem 1</ListBoxItem> <ListBoxItem>ListBoxItem 2</ListBoxItem> <ListBoxItem>ListBoxItem 3</ListBoxItem> <ListBoxItem>ListBoxItem 4</ListBoxItem> <ListBoxItem>ListBoxItem 5</ListBoxItem> <ListBoxItem>ListBoxItem 6</ListBoxItem> <ListBoxItem>ListBoxItem 7</ListBoxItem> <ListBoxItem>ListBoxItem 8</ListBoxItem> <ListBoxItem>ListBoxItem 9</ListBoxItem> <ListBoxItem>ListBoxItem 10</ListBoxItem> </ListBox> </Grid> </Window> |
7.2.3 Summary
本小节首先简单介绍了如何在.NET通过调用Win32 API来模拟键盘的操作,进而通过实例演示了模拟鼠标与模拟键盘在基于UI Automation的自动化测试中应用。