由于工作当中有部分任务需要使用到模拟键盘鼠标来完成业务的自动调用,虽然原来的同事有做了一些共用方法以及使用XML配置档来配置模拟动作流程,但是公用的方法和XML配置组合起来用的时候还是有不少的麻烦。
配置如下:
光从一些配置上,是比较难以理解的,个人觉得除了编写这个配置的本人或者使用一段时间的开发人员以外,其他开发人员需要介入修改或者重新制作配置是有一定难度的。也许大家会对业务处理有所期待,业务判断如下:
基本上就是属于对每个节点的Switch判断,通用的节点无非就那几个,但是涉及额外业务的时候,那就必须要设定一些其他的节点作为判断的依据,甚是复杂。
于是乎,在我进入公司差不多一个月的时间,我被分配到了这个任务上,尝试理解了配置、公用方法以及处理代码后,发现还是要重新制作一个新的模块,业务处理更清晰、配置起来更容易。
首先我是从配置入手的,其实每一个节点就是一个步骤,每一步无非就是抓取父窗体、从父窗体内抓取对应的子窗体、将鼠标定位在某一坐标或窗体上、输入值、获取值等等。
新配置如下:
新的配置试用了比较常用的名称作为节点的名字,对于配置也是考虑多种情况,而设置了不同的节点。root中的NameSpace属性是用来配置反射具体的业务类的。
Program:启动的程序,这是根据注册表读取的。
Form:是单独的窗体。
Child:窗体内部的子窗体或者控件,类似Label的除外。
KeyBoard:按键,单独的键位或者组合键。
ClickTo:根据具体的坐标或者当坐标数据不存在时会定位到上一节点窗体的中央,点击
If:判断节点,大家应该比较熟悉了,也就是当条件为真的情况下,将会执行节点内部的动作。
Each:与If类似,但是会循环执行。
Call:调用具体类方法的,配置图中未出现。
由于步骤都是通过读取XML每个节点的信息而触发动作的,而且部分子步骤需要调用父步骤窗体,因此我们需要提供一个基类方法给各个子类去实现,代码如下:
1 ///<summary> 2 /// 执行 3 ///</summary> 4 public abstract void Execute(); 5 6 ///<summary> 7 /// 初始化 8 ///</summary> 9 ///<param name="step">上一步</param> 10 ///<param name="node">节点</param> 11 public virtual void Init(AbstractStep step, XElement node) 12 { 13 this.ParentStep = step; 14 this.Node = node; 15 this.State = EnumStepState.Normal; 16 this.Message = string.Empty; 17 }
子类如下:
1 ///<summary> 2 /// 程序说明:移动 3 /// 创建作者:ahl 4 /// 创建时间:2011-10-31 5 ///</summary> 6 class MoveToStep : AbstractStep 7 { 8 #region 变量 9 10 ///<summary> 11 /// 横坐标 12 ///</summary> 13 int x = 0; 14 15 ///<summary> 16 /// 纵坐标 17 ///</summary> 18 int y = 0; 19 20 #endregion 21 22 #region 方法 23 24 ///<summary> 25 /// 执行方法 26 ///</summary> 27 public override void Execute() 28 { 29 if (this.ParentStep.Wnd != IntPtr.Zero) 30 { 31 var rect = this.ParentStep.Wnd.GetWindowRect(); 32 if (this.x == 0 && this.y == 0) 33 { 34 this.x = (rect.Left + rect.Right) / 2; 35 this.y = (rect.Top + rect.Bottom) / 2; 36 } 37 else 38 { 39 this.x += this.x < 0 ? rect.Right : rect.Left; 40 this.y += this.y < 0 ? rect.Bottom : rect.Top; 41 } 42 } 43 Hook.SetCursorPos(this.x, this.y); 44 } 45 46 ///<summary> 47 /// 初始化 48 ///</summary> 49 ///<param name="step">上一步</param> 50 ///<param name="node">节点</param> 51 public override void Init(AbstractStep step, XElement node) 52 { 53 base.Init(step, node); 54 var nodeValue = this.Node.Value; 55 if (!string.IsNullOrEmpty(nodeValue)) 56 { 57 var pos = nodeValue.Split(','); 58 this.x += Convert.ToInt32(pos[0]); 59 this.y += Convert.ToInt32(pos[1]); 60 } 61 } 62 63 #endregion 64 }
1 ///<summary> 2 /// 程序说明:移动至单击 3 /// 创建作者:ahl 4 /// 创建时间:2011-10-31 5 ///</summary> 6 class ClickToStep : AbstractStep 7 { 8 #region 变量 9 10 ///<summary> 11 /// 移动至 12 ///</summary> 13 MoveToStep moveTo; 14 15 #endregion 16 17 #region 方法 18 19 ///<summary> 20 /// 执行方法 21 ///</summary> 22 public override void Execute() 23 { 24 this.moveTo = new MoveToStep(); 25 this.moveTo.Init(this.ParentStep, this.Node); 26 this.moveTo.Execute(); 27 Hook.MouseEvent(MouseEventFlag.LeftDown | MouseEventFlag.LeftUp, 0, 0, 0, IntPtr.Zero); 28 } 29 30 #endregion 31 }
其他的就省略了,以免代码过长,呵呵。有了一些基本的步骤以及配置之后,这时我们就需要一个可以用来解析配置的类了。主要是解析节点、递归子节点、对于一些特殊的节点,如If、Each、Call需要做一些额外的处理,代码如下:
1 ///<summary> 2 /// 获取子节点步骤 3 ///</summary> 4 ///<param name="parentStep">父节点步骤对象</param> 5 ///<param name="childList">子节点列表</param> 6 ///<param name="nowList">子节点步骤列表</param> 7 void GetChildStep(AbstractStep parentStep, IEnumerable<XElement> childList, IList<AbstractStep> nowList) 8 { 9 foreach (var node in childList) 10 { 11 SimpleReflector normalReflector = new SimpleReflector(typeof(AbstractStep), string.Format("AHL.HookHelper.SMK.{0}Step", node.Name.LocalName)); 12 AbstractStep step = normalReflector.GetClassObject() as AbstractStep; 13 step.Init(parentStep, node); 14 nowList.Add(step); 15 switch (node.Name.LocalName) 16 { 17 case "Form": 18 case "Child": 19 this.GetChildStep(step, step.Node.Elements(), nowList); 20 break; 21 case "If": 22 case "Each": 23 var eachStep = step as SMK.IfStep; 24 this.GetChildStep(step.ParentStep, step.Node.Elements(), eachStep.StepList); 25 eachStep.SetProperty(this.reflector, this.simulation, this.interval); 26 break; 27 case "Call": 28 (step as SMK.CallStep).ReflectorMethod(this.reflector); 29 break; 30 } 31 } 32 }
到这里我们就把配置、节点以及解析等工作都完成了,于是乎就到了最后的阶段,那就是使用定时器对对当前的步骤进行定时操作就可以了,代码如下:
1 ///<summary> 2 /// 设置定时器事件 3 ///</summary> 4 void SetTimerTick() 5 { 6 this.timer.Tick += (_s, _e) => 7 { 8 var step = this.stepList[this.index++]; 9 try 10 { 11 step.Execute(); 12 switch (step.State) 13 { 14 case EnumStepState.Normal: 15 this.stepList.Count.If(l => l <= this.index, () => 16 { 17 this.index = 0; 18 this.Finish(EnumStepState.Normal, step.Message); 19 }); 20 break; 21 case EnumStepState.Back: 22 this.index--; 23 break; 24 default: 25 this.Finish(step.State, step.Message; 26 break; 27 } 28 } 29 catch (Exception ex) 30 { 31 this.Finish(EnumStepState.Exception, ex.Message); 32 } 33 }; 34 }
由于当前的版本只是对于键盘鼠标的模拟,但是当窗体使用类似Label的情况下,使用window api是没有办法抓取到此情况下的任何数据的,鉴于这种情况下,于是又开始了另一段新的学习,关于window api内关于进程内存的学习,等我完成之后再继续讨论吧,呵呵。