由於工作當中有部分任務需要使用到模擬鍵盤鼠標來完成業務的自動調用,雖然原來的同事有做了一些共用方法以及使用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內關於進程內存的學習,等我完成之後再繼續討論吧,呵呵
作者 ahl5esoft