使用C#制作可以录制自动化执行Windows操作脚本工具——类似于按键精灵

  • 使用C#制作可以录制自动化执行Windows操作脚本工具——类似于按键精灵已关闭评论
  • 131 次浏览
  • A+
所属分类:.NET技术
摘要

我们知道,如果要对一个网站进行自动化测试,可以使用Python的selenium对获取网页的元素进行一系列操作。同样,对于Windows应用,可以使用C#或者AutoIt(也是一种脚本语言,相比较与C#,AutoIt更适合做Windows应用的自动化脚本)捕获窗体句柄进行操作。

我们知道,如果要对一个网站进行自动化测试,可以使用Python的selenium对获取网页的元素进行一系列操作。同样,对于Windows应用,可以使用C#或者AutoIt(也是一种脚本语言,相比较与C#,AutoIt更适合做Windows应用的自动化脚本)捕获窗体句柄进行操作。

今天主要记录一下使用WPF制作可以录制自动化执行Windows操作脚本工具,类似于按键精灵的录制脚本的操作。主要使用勾子捕获鼠标键盘事件。

<Window x:Class="AutoOperationTool.MainWindow"         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"         xmlns:local="clr-namespace:AutoOperationTool"         mc:Ignorable="d"         Title="自动化脚本" Height="450" Width="800">     <Grid>         <Grid.RowDefinitions>             <RowDefinition></RowDefinition>             <RowDefinition></RowDefinition>             <RowDefinition></RowDefinition>         </Grid.RowDefinitions>         <Grid VerticalAlignment="Bottom" Margin="0 0 0 30">             <Grid.ColumnDefinitions>                 <ColumnDefinition></ColumnDefinition>                 <ColumnDefinition></ColumnDefinition>             </Grid.ColumnDefinitions>             <Label Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Right" Content="当前鼠标在屏幕的位置:"></Label>             <Grid Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Left">                 <Grid.ColumnDefinitions>                     <ColumnDefinition></ColumnDefinition>                     <ColumnDefinition></ColumnDefinition>                 </Grid.ColumnDefinitions>                 <Grid Grid.Column="0">                     <Grid.ColumnDefinitions>                         <ColumnDefinition></ColumnDefinition>                         <ColumnDefinition></ColumnDefinition>                     </Grid.ColumnDefinitions>                     <Label  Content="X:"></Label>                     <TextBlock x:Name="xPoint" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>                 </Grid>                 <Grid Grid.Column="1">                     <Grid.ColumnDefinitions>                         <ColumnDefinition></ColumnDefinition>                         <ColumnDefinition></ColumnDefinition>                     </Grid.ColumnDefinitions>                     <Label  Content="Y:"></Label>                     <TextBlock x:Name="yPoint" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>                 </Grid>             </Grid>         </Grid>         <Grid Grid.Row="1">             <Grid.ColumnDefinitions>                 <ColumnDefinition></ColumnDefinition>                 <ColumnDefinition></ColumnDefinition>             </Grid.ColumnDefinitions>             <Grid Grid.Column="0">                 <Grid.ColumnDefinitions>                     <ColumnDefinition></ColumnDefinition>                     <ColumnDefinition></ColumnDefinition>                 </Grid.ColumnDefinitions>                 <Label Content="设置循环次数:" VerticalAlignment="Center" HorizontalAlignment="Right"></Label>                 <Border Grid.Column="1" Width="120" Height="35" BorderBrush="Black" HorizontalAlignment="Left" BorderThickness="1">                     <TextBox Width="120" Height="35" x:Name="txtCycleCount" Text="1" VerticalAlignment="Center" HorizontalAlignment="Center"></TextBox>                 </Border>             </Grid>             <Grid Grid.Column="2">                 <Grid.ColumnDefinitions>                     <ColumnDefinition></ColumnDefinition>                     <ColumnDefinition Width="320"></ColumnDefinition>                 </Grid.ColumnDefinitions>                 <Label Content="脚本路径:" VerticalAlignment="Center" HorizontalAlignment="Right"></Label>                 <Border Grid.Column="1" Width="310" Height="35" BorderBrush="Black" HorizontalAlignment="Left" BorderThickness="1">                     <TextBox Width="310" Height="35" x:Name="txtScriptPath" VerticalAlignment="Center" HorizontalAlignment="Center" TextWrapping="Wrap"></TextBox>                 </Border>             </Grid>         </Grid>         <Grid Grid.Row="2" Margin="0 30 0 0" VerticalAlignment="Top">             <Grid.ColumnDefinitions>                 <ColumnDefinition></ColumnDefinition>                 <ColumnDefinition></ColumnDefinition>                 <ColumnDefinition></ColumnDefinition>                 <ColumnDefinition></ColumnDefinition>             </Grid.ColumnDefinitions>             <Button Grid.Column="0" x:Name="btnStart" Width="100" Height="40" Content="开始录制" Click="Start_OnClick"></Button>             <Button Grid.Column="1" x:Name="btnEnd" Width="100" Height="40" Content="结束录制" Click="End_OnClick"></Button>             <Button Grid.Column="2" x:Name="btnExec" Width="100" Height="40" Content="执行脚本" Click="Exec_OnClick"></Button>             <Button Grid.Column="3" x:Name="btnCancel" Width="100" Height="40" Content="取消执行" Click="CancelExec_OnClick"></Button>         </Grid>     </Grid> </Window>

using System; using System.Collections.Generic; using System.Configuration; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows; using Newtonsoft.Json; using Path = System.IO.Path;  namespace AutoOperationTool {     /// <summary>     /// MainWindow.xaml 的交互逻辑     /// </summary>     public partial class MainWindow : Window     {         // 用于取消任务的执行         private CancellationTokenSource cancelTokenSource = new CancellationTokenSource();          private Task ExecTask { get; set; }          private KeyAction KeyAction { get; set; }          public MainWindow()         {             InitializeComponent();             Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);             txtScriptPath.Text = config.AppSettings.Settings["path"].Value;             KeyAction = new KeyAction();             btnEnd.IsEnabled = false;             btnCancel.IsEnabled = false;         }          private bool KMHook_MouseCallback(object arg)         {             if (arg is long[] arrs && arrs.Length == 3)             {                 var type = arrs[0];                 var x = arrs[1];                 var y = arrs[2];                 if (type == 1)                 {                     var model = new MouseOperation();                     model.MouseOperationType = MouseOperationTypeEnum.Click;                     model.Point = new Point(){ X = x, Y = y};                     model.Time = DateTime.Now;                     model.OperationType = OperationType.Mouse;                     PrintLog.WriteTxt.GetInstance().AppendInfoLog(JsonConvert.SerializeObject(model));                 }                  xPoint.Text = x.ToString();                 yPoint.Text = y.ToString();                 Console.WriteLine($"X:{x};Y:{y}");             }              return true;         }          private bool KMHook_KeyCallback(object arg)         {             if (arg is long[] arrs && arrs.Length == 3)             {                 var type = arrs[0];                 var code = arrs[1];                 var time = arrs[2];                 var model = new KeyOperation();                 if (type == 0)                 {                     model.KeyOperationType = KeyOperationTypeEnum.Press;                 }                 else if (type == 1)                 {                     model.KeyOperationType = KeyOperationTypeEnum.Up;                 }                  model.KeyCode = (Key)Enum.Parse(typeof(Key), code.ToString());                 model.OperationType = OperationType.Key;                 model.Time = DateTime.Now;                 PrintLog.WriteTxt.GetInstance().AppendInfoLog(JsonConvert.SerializeObject(model));             }              return true;         }          private void Start_OnClick(object sender, RoutedEventArgs e)         {             btnEnd.IsEnabled = true;             btnStart.IsEnabled = false;             btnExec.IsEnabled = false;             btnCancel.IsEnabled = false;              // 如果存在脚本名称,序号往前加             PrintLog.WriteTxt.GetInstance().FileLogName = "script1.txt";             var fileLogName = PrintLog.WriteTxt.GetInstance().FileLogName;             if (File.Exists(PrintLog.WriteTxt.GetInstance().FileStartupPath + PrintLog.WriteTxt.GetInstance().FileLogName))             {                 var fileName = PrintLog.WriteTxt.GetInstance().FileLogName;                 while (File.Exists(PrintLog.WriteTxt.GetInstance().FileStartupPath + fileName))                 {                     if (fileName.StartsWith("script") && fileName.EndsWith(".txt"))                     {                         var strCount = fileName.Replace("script", "").Replace(".txt", "");                         int count;                         if (int.TryParse(strCount, out count))                         {                             count++;                             fileName = $"script{count}.txt";                         }                     }                     else                     {                         Directory.Delete(PrintLog.WriteTxt.GetInstance().FileStartupPath + PrintLog.WriteTxt.GetInstance().FileLogName);                         break;                     }                 }                  fileLogName = fileName;             }              PrintLog.WriteTxt.GetInstance().FileLogName = fileLogName;             txtScriptPath.Text = Path.Combine(PrintLog.WriteTxt.GetInstance().FileStartupPath, fileLogName);             Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);             config.AppSettings.Settings["path"].Value = txtScriptPath.Text;             config.Save();             KMHook.MouseCallback += KMHook_MouseCallback;             KMHook.KeyCallback += KMHook_KeyCallback;             KMHook.InsertHook();         }          private void End_OnClick(object sender, RoutedEventArgs e)         {             KMHook.MouseCallback -= KMHook_MouseCallback;             KMHook.KeyCallback -= KMHook_KeyCallback;             KMHook.RemoveHook();             btnStart.IsEnabled = true;             btnEnd.IsEnabled = !btnStart.IsEnabled;             btnExec.IsEnabled = true;             btnCancel.IsEnabled = !btnExec.IsEnabled;         }          private void Exec_OnClick(object sender, RoutedEventArgs e)         {             btnStart.IsEnabled = false;             btnEnd.IsEnabled = !btnStart.IsEnabled;             btnExec.IsEnabled = false;             btnCancel.IsEnabled = !btnExec.IsEnabled;             var listOperations = new List<Operation>();             var path = txtScriptPath.Text;             var listStrs = File.ReadLines(path)?.ToList();             if (listStrs != null && listStrs.Count > 0)             {                 foreach (var strScript in listStrs)                 {                     try                     {                         var operation = JsonConvert.DeserializeObject<Operation>(strScript);                         if (operation.OperationType == OperationType.Mouse)                         {                             var mouseOperation = JsonConvert.DeserializeObject<MouseOperation>(strScript);                             listOperations.Add(mouseOperation);                         }                         else if (operation.OperationType == OperationType.Key)                         {                             var keyOperation = JsonConvert.DeserializeObject<KeyOperation>(strScript);                             listOperations.Add(keyOperation);                         }                     }                     catch (Exception ex)                     {                         throw ex;                     }                 }             }              int count;             if (int.TryParse(txtCycleCount.Text, out count))             {                 ExecTask = Task.Factory.StartNew(() =>                 {                     if (count < 1) count = 1;                     for (int i = 0; i < count; i++)                     {                         DateTime lastTime = new DateTime();                         DateTime nextTime = new DateTime();                         for (int j = 0; j < listOperations.Count; j++)                         {                             if (lastTime == new DateTime())                             {                                 lastTime = listOperations[j].Time;                                  Exec(listOperations, j);                             }                             else                             {                                 nextTime = listOperations[j].Time;                                 if (j > 0)                                 {                                     lastTime = listOperations[j - 1].Time;                                 }                                  Thread.Sleep(nextTime - lastTime);                                 Exec(listOperations, j);                             }                         }                          Thread.Sleep(1000);                     }                      Application.Current.Dispatcher.Invoke(() =>                     {                         btnStart.IsEnabled = true;                         btnEnd.IsEnabled = !btnStart.IsEnabled;                         btnExec.IsEnabled = true;                         btnCancel.IsEnabled = !btnExec.IsEnabled;                     });                 }, cancelTokenSource.Token);             }         }          private void Exec(List<Operation> listOperations, int j)         {             if (listOperations[j].OperationType == OperationType.Mouse)             {                 var mouse = listOperations[j] as MouseOperation;                 MouseAction.DoClick((int)mouse.Point.X, (int)mouse.Point.Y);             }             else if (listOperations[j].OperationType == OperationType.Key)             {                 var key = listOperations[j] as KeyOperation;                 if (key.KeyOperationType == KeyOperationTypeEnum.Press)                 {                     KeyAction.MykeyDown(key.KeyCode);                 }                 else if (key.KeyOperationType == KeyOperationTypeEnum.Up)                 {                     KeyAction.MykeyUp(key.KeyCode);                 }             }         }          private void CancelExec_OnClick(object sender, RoutedEventArgs e)         {             if (ExecTask != null)             {                 for (int i = 0; i < 3; i++)                 {                     try                     {                         cancelTokenSource.Cancel();                         if (cancelTokenSource.IsCancellationRequested)                         {                             cancelTokenSource = new CancellationTokenSource();                             ExecTask.Dispose();                             btnStart.IsEnabled = true;                             btnEnd.IsEnabled = !btnStart.IsEnabled;                             btnExec.IsEnabled = true;                             btnCancel.IsEnabled = !btnExec.IsEnabled;                             break;                         }                     }                     catch (Exception)                     {                     }                 }             }         }     } }

勾子监听键盘鼠标事件

using System; using System.Runtime.InteropServices;  namespace AutoOperationTool {     public class KMHook     {         public static bool InsertHook()         {             bool iRet;             iRet = InsertKeyboardHook();             if (!iRet)             {                 return false;             }              iRet = InsertMouseHook();             if (!iRet)             {                 removeKeyboardHook();                 return false;             }              return true;         }          public static bool RemoveHook()         {             bool iRet;             iRet = removeKeyboardHook();             if (iRet)             {                 iRet = removeMouseHook();             }              return iRet;         }          public static event Func<object, bool> MouseCallback;         public static event Func<object, bool> KeyCallback;          internal struct Keyboard_LL_Hook_Data         {             public UInt32 vkCode;             public UInt32 scanCode;             public UInt32 flags;             public UInt32 time;             public IntPtr extraInfo;         }          internal struct Mouse_LL_Hook_Data         {             internal long yx;             internal readonly int mouseData;             internal readonly uint flags;             internal readonly uint time;             internal readonly IntPtr dwExtraInfo;         }          private static IntPtr pKeyboardHook = IntPtr.Zero;         private static IntPtr pMouseHook = IntPtr.Zero;         //钩子委托声明         public delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);         private static HookProc keyboardHookProc;         private static HookProc mouseHookProc;          //安装钩子         [DllImport("user32.dll")]         public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr pInstance, int threadID);          //卸载钩子         [DllImport("user32.dll", CallingConvention = CallingConvention.StdCall)]         public static extern bool UnhookWindowsHookEx(IntPtr pHookHandle);         [DllImport("user32.dll")]         public static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); //parameter 'hhk' is ignored.          private static int keyboardHookCallback(int code, IntPtr wParam, IntPtr lParam)         {             if (code < 0)             {                 return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);             }              Keyboard_LL_Hook_Data khd = (Keyboard_LL_Hook_Data)Marshal.PtrToStructure(lParam, typeof(Keyboard_LL_Hook_Data));             System.Diagnostics.Debug.WriteLine($"key event:{wParam}, key code:{khd.vkCode}, event time:{khd.time}");              var keyType = 0L;             var iWParam = (int)wParam;             if (iWParam == 256)             {                 keyType = 0;             }             else if (iWParam == 257)             {                 keyType = 1;             }             else             {                 keyType = 0;             }             KeyCallback?.Invoke(new long[3] { keyType, (int)khd.vkCode, (int)khd.time });             return 0;         }          private static int mouseHookCallback(int code, IntPtr wParam, IntPtr lParam)         {             if (code < 0)             {                 return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);             }              Mouse_LL_Hook_Data mhd = (Mouse_LL_Hook_Data)Marshal.PtrToStructure(lParam, typeof(Mouse_LL_Hook_Data));             System.Diagnostics.Debug.WriteLine($"mouse event:{wParam}, ({mhd.yx & 0xffffffff},{mhd.yx >> 32})");             var mouseType = 0L;             var iWParam = (int)wParam;             if (iWParam == 513)             {                 mouseType = 1;             }             else if (iWParam == 514)             {                 mouseType = 2;             }             else             {                 mouseType = 0;             }              MouseCallback?.Invoke(new long[3]{ mouseType , mhd.yx & 0xffffffff, mhd.yx >> 32 });             return 0;         }          //安装钩子方法         private static bool InsertKeyboardHook()         {             if (pKeyboardHook == IntPtr.Zero)//不存在钩子时             {                 //创建钩子                 keyboardHookProc = keyboardHookCallback;                 pKeyboardHook = SetWindowsHookEx(13, //13表示全局键盘事件。                     keyboardHookProc,                     (IntPtr)0,                     0);                  if (pKeyboardHook == IntPtr.Zero)//如果安装钩子失败                 {                     removeKeyboardHook();                     return false;                 }             }              return true;         }          private static bool InsertMouseHook()         {             if (pMouseHook == IntPtr.Zero)             {                 mouseHookProc = mouseHookCallback;                 pMouseHook = SetWindowsHookEx(14, //14表示全局鼠标事件                     mouseHookProc,                     (IntPtr)0,                     0);                  if (pMouseHook == IntPtr.Zero)                 {                     removeMouseHook();                     return false;                 }             }              return true;         }          private static bool removeKeyboardHook()         {             if (pKeyboardHook != IntPtr.Zero)             {                 if (UnhookWindowsHookEx(pKeyboardHook))                 {                     pKeyboardHook = IntPtr.Zero;                 }                 else                 {                     return false;                 }             }              return true;         }          private static bool removeMouseHook()         {             if (pMouseHook != IntPtr.Zero)             {                 if (UnhookWindowsHookEx(pMouseHook))                 {                     pMouseHook = IntPtr.Zero;                 }                 else                 {                     return false;                 }             }              return true;         }     } }

操作键盘按键

using System; using System.Runtime.InteropServices;  namespace AutoOperationTool {     public class KeyAction     {         [DllImport("user32.dll", SetLastError = true)]         public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);          /// key down         public void MykeyDown(Key vKeyCoad)         {             PressKey(vKeyCoad, false);         }          /// Key up         public void MykeyUp(Key vKeyCoad)         {             PressKey(vKeyCoad, true);         }          private void PressKey(Key key, bool up)         {             const int KEYEVENTF_EXTENDEDKEY = 0x1;             const int KEYEVENTF_KEYUP = 0x2;             if (up)             {                 keybd_event((byte)key, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, (UIntPtr)0);             }             else             {                 keybd_event((byte)key, 0x45, KEYEVENTF_EXTENDEDKEY, (UIntPtr)0);             }         }     } }

操作鼠标移动及单击

using System.Runtime.InteropServices;  namespace AutoOperationTool {     public class MouseAction     {         [DllImport("user32")]         public static extern int mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);          [DllImport("User32.dll")]         public static extern bool SetCursorPos(int X, int Y);          public static void DoClick(int x, int y)         {             SetCursorPos(x, y);             mouse_event((int)MouseType.MOUSEEVENTF_LEFTDOWN | (int)MouseType.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);         }     } }