來到了新公司,一開始就要作個程序去獲取另一個程序裏的數據,哇,挑戰性很大。html
通過兩週的學習,終於搞定,主要仍是對Windows API有了更多的瞭解。ide
文中全部的消息常量,API,結構體都整理出來了(還不是很全):Windows.zip post
目錄:學習
正文:測試
一丶怎麼獲取每一個控件的句柄ui
第一種是使用FindWindow和FindWindowEx兩個API結合使用,但太累太繁瑣,不爽。this
說實話第一次我是經過Spy++看我所須要的控件的順序,而後循環幾回獲取這個控件的句柄,顯然這種方式很麻煩。url
第二種是在網上看到了另外一種獲取控件句柄的方式:spa
①每一個控件都有惟一的ControlIDcode
②經過Spy++查看每個ControlID,並經過EnumChildWindows來循環每個控件
代碼以下:
1 public class ExportForm : BaseForm 2 { 3 private string _userID = string.Empty; 4 private IntPtr _cancelButtonHandle = IntPtr.Zero;
10 11 private readonly int _cancelButtonControlID = Convert.ToInt32("00000002", 16); //經過Spy++獲取你想要的ControlID 12 private readonly int _confirmButtonControlID = Convert.ToInt32("00000001", 16);
17 18 public ExportForm(string userID) 19 { 20 this._userID = userID; 21 } 43 public override sealed void GetAllHandles() 44 { 45 base.LoadFormHandle(null, CITICConfigInfo.ExportFormName); 46 WindowsAPI.EnumChildWindows(base.FormHandle, (handle, param) => //這個API是循環窗體中的全部控件 47 { 48 int flagControlID = WindowsAPI.GetWindowLong(handle, WindowsConst.GWL_ID); //經過句柄獲取ControlID 49 50 if (flagControlID == this._cancelButtonControlID) 51 { 52 this._cancelButtonHandle = handle; 53 }
74 75 return true; 76 }, 0); 77 } 78 }
二丶模擬鍵盤和鼠標
在一些特殊的狀況下,沒有辦法發送消息通知控件觸發單擊事件(或其它事件),只能經過模擬鍵盤和鼠標來操做了。
關於Hook的學習請看 - 學習之路三十八:Hook(鉤子)的學習
①mouse_event - 鼠標操做
②keybd_event - 觸發鍵盤
API的定義:
1 /// <summary> 2 /// 鍵盤操做指令 3 /// </summary> 4 /// <param name="bVk">鍵盤指令:Enter,F1等鍵盤按鈕,使用Keys枚舉便可</param> 5 /// <param name="bScan">默認都爲 - 0</param> 6 /// <param name="dwFlags">默認都爲 - 0</param> 7 /// <param name="dwExtraInfo">默認都爲 - 0</param> 8 [DllImport("user32.dll")] 9 public static extern void keybd_event(Byte bVk, Byte bScan, Int32 dwFlags, Int32 dwExtraInfo); 10 11 /// <summary> 12 /// 設置鼠標操做指令 13 /// </summary> 14 /// <param name="flag">指令類型:單擊,移動,雙擊</param> 15 /// <param name="x">X座標的位置</param> 16 /// <param name="y">Y座標的位置</param> 17 /// <param name="cButtons">默認都爲 - 0</param> 18 /// <param name="dwExtraInfo">默認都爲 - 0</param> 19 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] 20 public static extern void mouse_event(int flag, int x, int y, int cButtons, int dwExtraInfo);
關於鼠標API的調用:
1 Rectangle rectangle; 2 WindowsAPI.GetWindowRect(this._treeViewHandle, out rectangle); 3 WindowsAPI.SetCursorPos(rectangle.Left + 55, rectangle.Top + 220); 4 WindowsAPI.mouse_event(WindowsConst.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0); //第一個參數爲消息指令 5 WindowsAPI.mouse_event(WindowsConst.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
三丶給文本框賦值
在Windows API我感受比較重要的方法是SendMessage,幾乎全部的發送指令都須要用到它,把它用好就成功了一大半了(瞎說的)。
其實經過Reflector查看.NET源碼,發現內部方法中SendMessage有將近20個重載方法,不少,舉其中一個列子:
1 /// <summary> 2 /// 給句柄發送指令消息,等待消息處理完成 3 /// </summary> 4 /// <param name="handle">指定句柄</param> 5 /// <param name="message">消息指令:如click</param> 6 /// <param name="wParam">默認都爲 - 0</param> 7 /// <param name="lParam">默認都爲 - 0</param> 8 /// <returns>返回的結果</returns> 9 [DllImport("user32.dll")] 10 public static extern UInt32 SendMessage(IntPtr handle, UInt32 message, UInt32 wParam, UInt32 lParam); 11 12 //調用方式 13 WindowsAPI.SendMessage(this._passwordHandle, WindowsConst.WM_SETTEXT, 0, "這是我發送的消息"); //最後一個參數是給文本框賦值內容
四丶操做DatetimePicker控件
操做日期控件我查找資料搞了很久,原來它並非僅僅發送一個消息就能夠搞定的,我猜想大多數複雜控件要觸發事件確定不能用SendMessage就覺得搞定了。
原來要想給控件賦值必須用到操做內存的方式,代碼以下:
步驟:①根據句柄獲取進程ID
②打開進程並獲取進程句柄
③在進程中申請內存空間並返回申請的內存地址
④把數據寫入到剛剛開闢的內存空間去
⑤發送消息通知日期控件更新數據
⑥釋放內存
1 private void OperationDateTimePicker() 2 { 3 SYSTEMTIME time = new SYSTEMTIME { wYear = 1990, wMonth = 10, wDay = 10 }; 4 5 int objSize = Marshal.SizeOf(typeof(SYSTEMTIME)); 6 byte[] buffer = new byte[objSize]; 7 IntPtr flagHandle = Marshal.AllocHGlobal(objSize); 8 Marshal.StructureToPtr(time, flagHandle, true); 9 Marshal.Copy(flagHandle, buffer, 0, objSize); 10 Marshal.FreeHGlobal(flagHandle); 11 12 //①獲取遠程進程ID 13 int processID = default(int); 14 WindowsAPI.GetWindowThreadProcessId(this._startTimeHandle, ref processID); 15 //②獲取遠程進程句柄 16 IntPtr processHandle = WindowsAPI.OpenProcess(WindowsConst.PROCESS_ALL_ACCESS, false, processID); 17 //③在遠程進程申請內存空間並返回內存地址 18 IntPtr memoryAddress = WindowsAPI.VirtualAllocEx(processHandle, IntPtr.Zero, objSize, WindowsConst.MEM_COMMIT, WindowsConst.PAGE_READWRITE); 19 //④把數據寫入上一步申請的內存空間 20 WindowsAPI.WriteProcessMemory(processHandle, memoryAddress, buffer, buffer.Length, 0); 21 //⑤發送消息給句柄叫它更新數據 22 WindowsAPI.SendMessage(this._startTimeHandle, WindowsConst.DTM_SETSYSTEMTIME, WindowsConst.GDT_VALID, memoryAddress); 23 //⑥釋放內存並關閉句柄 24 WindowsAPI.VirtualFreeEx(processHandle, memoryAddress, objSize, WindowsConst.MEM_RELEASE); 25 WindowsAPI.CloseHandle(processHandle); 26 }
PS:感受往C++方面靠近了,學起來真心不容易啊,難怪說C++入門困難,領會到了,o(∩_∩)o 。
五丶操做TreeView控件
說實話對於怎麼觸發TreeView的Node單擊事件我尚未找到資料,但願會的朋友告訴我,我感激涕零。
首先獲取TreeView控件的句柄是首要條件。
①獲取根節點
1 //①獲取根節點 2 int rootNodeNum = WindowsAPI.SendMessage(this._treeViewHandle, WindowsConst.TVM_GETNEXTITEM, WindowsConst.TVGN_ROOT, 0); 3 IntPtr rootNodeHandle = new IntPtr(rootNodeNum);
②選中根節點
1 //②選中根節點 2 WindowsAPI.SendMessage(this._treeViewHandle, WindowsConst.TVM_SELECTITEM, WindowsConst.TVGN_CARET, rootNodeHandle);
③獲取指定節點句柄
1 //③遍歷全部一級節點,獲取我想要的節點句柄 2 IntPtr selectNodeHandle = rootNodeHandle; 3 for (int num = 1; num <= 6; num++) //記住節點的順序,我想要的節點位置在第六個上 4 { 5 int flagNodeNum = WindowsAPI.SendMessage(this._treeViewHandle, WindowsConst.TVM_GETNEXTITEM, WindowsConst.TVGN_NEXT, selectNodeHandle); 6 selectNodeHandle = new IntPtr(flagNodeNum); 7 }
④選中並展開節點
1 //④展開節點 2 WindowsAPI.SendMessage(this._treeViewHandle, WindowsConst.TVM_SELECTITEM, WindowsConst.TVGN_CARET, selectNodeHandle); //最後一個參數爲第三步獲取的節點句柄 3 WindowsAPI.SendMessage(this._treeViewHandle, WindowsConst.TVM_EXPAND, WindowsConst.TVE_EXPAND, selectNodeHandle);
⑤尋找二級節點:注意消息常量的運用
1 //⑤繼續循環當前節點,獲取我想要的二級節點 2 IntPtr childrenNodeHandle = selectNodeHandle; 3 for (int num = 1; num <= 5; num++) 4 { 5 int flagNode = WindowsAPI.SendMessage(this._treeViewHandle, WindowsConst.TVM_GETNEXTITEM, WindowsConst.TVGN_CHILD, childrenNodeHandle); 6 childrenNodeHandle = new IntPtr(flagNode); 7 }
⑥節點的單擊事件
說實話我沒有真正的經過發送消息來實現事件通知,只有經過模擬鼠標來操做的,但願懂得朋友教教我。
1 //⑥單擊節點--模擬鼠標單擊 2 Rectangle rectangle; 3 WindowsAPI.GetWindowRect(this._treeViewHandle, out rectangle); 4 WindowsAPI.SetCursorPos(rectangle.Left + 55, rectangle.Top + 220); 5 WindowsAPI.mouse_event(WindowsConst.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0); 6 WindowsAPI.mouse_event(WindowsConst.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
六丶識別簡單驗證碼
圖像識別這個領域高深莫測,不是那麼容易搞得定的,前幾天搞了一個對簡單驗證碼的識別。
基本步驟是:
①獲取圖片對象
②進行灰度化處理
③閾值 - 也就是換成白底黑字的圖片
④分割圖片
⑤處理每一張分割的圖片(獲取黑色像素點)
★:由於個人驗證相對比較簡單(純數字,只是加了一些顏色干擾),我採起了一種簡潔方式,經過觀察我發現每一個數字佔的像素點都是一致的,因此前期我都把每一個數字佔的像素點都算出來了。
從面前測試狀況下來看正確率爲100%。
1 <add key="0" value="38"/> 2 <add key="1" value="19"/> 3 <add key="2" value="37"/> 4 <add key="3" value="52"/> 5 <add key="4" value="50"/> 6 <add key="5" value="35"/> 7 <add key="6" value="41"/> 8 <add key="7" value="28"/> 9 <add key="8" value="64"/> 10 <add key="9" value="42"/>
代碼以下(展不開,從新刷新下就能夠了):
1 namespace BLL 2 { 3 public class IdentifyingCode 4 { 5 private string BlackFlag = "1"; 6 private string WhiteFlag = "0"; 7 private Dictionary<int, string> Values; 8 public static readonly IdentifyingCode Instance = new IdentifyingCode(); 9 10 private IdentifyingCode() 11 { 12 this.Values = this.LoadData(); 13 } 14 15 private Dictionary<int, string> LoadData() 16 { 17 Dictionary<int, string> values = new Dictionary<int, string>(10); 18 for (int index = 0; index < 10; index++) 19 { 20 string value = index.ToString(CultureInfo.InvariantCulture); 21 string key = ConfigurationManager.AppSettings[value]; 22 values.Add(key.ToInt32(), value); 23 } 24 return values; 25 } 26 27 /// <summary> 28 /// 獲取驗證碼內容 29 /// </summary> 30 /// <param name="filePath">圖片路徑</param> 31 /// <returns>驗證碼值</returns> 32 public string Check(string filePath) 33 { 34 return this.Check(new Bitmap(filePath)); 35 } 36 37 /// <summary> 38 /// 獲取驗證碼內容 39 /// </summary> 40 /// <param name="bitmap">圖片對象</param> 41 /// <returns>驗證碼值</returns> 42 public string Check(Bitmap bitmap) 43 { 44 using (bitmap) 45 { 46 this.Gray(bitmap); 47 this.Threshold(bitmap, 190); 48 List<TempSize> tempSizes = this.Carve(bitmap); 49 return this.GetValue(bitmap, tempSizes.ToArray()); 50 } 51 } 52 53 /// <summary> 54 /// 灰度化 55 /// </summary> 56 /// <param name="bitmap">圖片對象</param> 57 private void Gray(Bitmap bitmap) 58 { 59 for (int x = 0; x < bitmap.Width; x++) 60 { 61 for (int y = 0; y < bitmap.Height; y++) 62 { 63 int grayNumber = GetGrayNumber(bitmap.GetPixel(x, y)); 64 bitmap.SetPixel(x, y, Color.FromArgb(grayNumber, grayNumber, grayNumber)); 65 } 66 } 67 } 68 69 /// <summary> 70 /// 獲取灰度化的臨界點 71 /// </summary> 72 /// <param name="color">每一個像素的顏色對象</param> 73 /// <returns>臨界值</returns> 74 private int GetGrayNumber(Color color) 75 { 76 return (int)(color.R * 0.3 + color.G * 0.59 + color.B * 0.11); 77 } 78 79 /// <summary> 80 /// 閾值,也就是轉換成白底黑字的圖片 81 /// </summary> 82 /// <param name="bitmap">圖片對象</param> 83 /// <param name="criticalValue">臨界值</param> 84 private void Threshold(Bitmap bitmap, int criticalValue) 85 { 86 for (int x = 0; x < bitmap.Width; x++) 87 { 88 for (int y = 0; y < bitmap.Height; y++) 89 { 90 Color color = bitmap.GetPixel(x, y); 91 bitmap.SetPixel(x, y, color.R >= criticalValue ? Color.White : Color.Black); 92 } 93 } 94 } 95 96 /// <summary> 97 /// 分割圖片 98 /// </summary> 99 /// <param name="originalBitmap">圖片對象</param> 100 /// <returns>每一個圖片的範圍</returns> 101 private List<TempSize> Carve(Bitmap originalBitmap) 102 { 103 string blackPointFlags = GetBlackPointFlags(originalBitmap); 104 105 bool flag = true; 106 int xStart = default(int); 107 List<TempSize> tempSizes = new List<TempSize>(); 108 109 for (int x = 0; x < originalBitmap.Width; x++) 110 { 111 if (blackPointFlags.Substring(x, 1) == BlackFlag) 112 { 113 if (flag) 114 { 115 flag = false; 116 xStart = x; 117 } 118 if (x < originalBitmap.Width) 119 { 120 if (blackPointFlags.Substring(x + 1, 1) == WhiteFlag) 121 { 122 int xEnd = x; 123 TempSize tempSize = new TempSize 124 { 125 XStart = xStart, 126 XWidth = (xEnd - xStart) + 1 127 }; 128 tempSizes.Add(tempSize); 129 } 130 } 131 } 132 else 133 { 134 flag = true; //從新開始 135 } 136 } 137 return tempSizes; 138 } 139 140 private string GetBlackPointFlags(Bitmap originalBitmap) 141 { 142 string everyColumnHasBlackPoints = string.Empty; 143 144 for (int x = 0; x < originalBitmap.Width; x++) 145 { 146 for (int y = 0; y < originalBitmap.Height; y++) 147 { 148 if (originalBitmap.GetPixel(x, y).R == Color.Black.R) 149 { 150 everyColumnHasBlackPoints += "1"; 151 break; 152 } 153 } 154 if (everyColumnHasBlackPoints.Length != x + 1) 155 { 156 everyColumnHasBlackPoints += "0"; 157 } 158 } 159 return everyColumnHasBlackPoints; 160 } 161 162 private string GetValue(Bitmap originalBitmap, TempSize[] tempSizes) 163 { 164 string result = string.Empty; 165 for (int index = 0; index < tempSizes.Length; index++) 166 { 167 string pointValues = string.Empty; 168 TempSize tempSize = tempSizes[index]; 169 for (int x = tempSize.XStart; x < tempSize.XStart + tempSize.XWidth; x++) 170 { 171 for (int y = 0; y < originalBitmap.Height; y++) 172 { 173 var color = originalBitmap.GetPixel(x, y); 174 pointValues += color.R == 0 ? "1" : "0"; 175 } 176 } 177 178 int blackPointCount = pointValues.Count(p => p == '1'); 179 if (this.Values.ContainsKey(blackPointCount)) 180 { 181 result += this.Values[blackPointCount]; 182 } 183 } 184 return result; 185 } 186 } 187 188 public struct TempSize 189 { 190 public int XStart; 191 public int XWidth; 192 } 193 }
七丶判斷按鈕的狀態
當我點擊一個按鈕去查詢數據的時候,可能要花點時間,因此會把按鈕的狀態設置爲不可用,那麼Windows API是這樣調用的:
1 //這邊我須要檢查「查詢」按鈕的狀態,若是爲灰色要等待,不然繼續下去 2 bool selectButtonStatus = false; 3 while (selectButtonStatus == false) 4 { 5 selectButtonStatus = WindowsAPI.IsWindowEnabled(this._selectButtonHandle); 6 }
就這麼多了,也只是把用到的記錄了一下,沒用到也不去學它,:-)。
以同步至:我的文章目錄索引