學習之路三十九:新手學習 - Windows API


來到了新公司,一開始就要做個程序去獲取另外一個程序里的數據,哇,挑戰性很大。

經過兩周的學習,終於搞定,主要還是對Windows API有了更多的了解。

文中所有的消息常量,API,結構體都整理出來了(還不是很全):Windows.zip 

 

目錄:

  1. 獲取控件句柄
  2. 模擬鍵盤和鼠標
  3. 文本框賦值
  4. 操作DateTimePicker控件
  5. 操作TreeView控件
  6. 識別簡單驗證碼
  7. 判斷按鈕狀態

 

正文:

一丶怎么獲取每個控件的句柄

  第一種是使用FindWindow和FindWindowEx兩個API結合使用,但太累太繁瑣,不爽。

  說實話第一次我是通過Spy++看我所需要的控件的順序,然后循環幾次獲取這個控件的句柄,顯然這種方式很麻煩。

  第二種是在網上看到了另一種獲取控件句柄的方式:

  ①每個控件都有唯一的ControlID

  ②通過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 }
View Code

 

七丶判斷按鈕的狀態

  當我點擊一個按鈕去查詢數據的時候,可能要花點時間,所以會把按鈕的狀態設置為不可用,那么Windows API是這樣調用的:

1     //這邊我需要檢查“查詢”按鈕的狀態,如果為灰色要等待,否則繼續下去
2     bool selectButtonStatus = false;
3     while (selectButtonStatus == false)
4     {
5         selectButtonStatus = WindowsAPI.IsWindowEnabled(this._selectButtonHandle);
6     }

 

就這么多了,也只是把用到的記錄了一下,沒用到也不去學它,:-)。

以同步至:個人文章目錄索引 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM