• 学习之路三十九:新手学习


    来到了新公司,一开始就要做个程序去获取另外一个程序里的数据,哇,挑战性很大。

    经过两周的学习,终于搞定,主要还是对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     }

    就这么多了,也只是把用到的记录了一下,没用到也不去学它,:-)。

    以同步至:个人文章目录索引 

  • 相关阅读:
    1、三八妇女节
    16、领导休假了,如何让领导帮忙签一下考勤呢?
    30、如何获取百分号前面的数字?
    29、如何在单元格里面添加单位?
    13、笔记本中的Excel 如何锁定单元格的$符号?
    28、把鼠标放在Excel的单元格中,单元格右下角十字架不能往下拖动
    31、如何把公式和单位合并在一起?
    27、Excel高级筛选
    2、中秋节
    9、香港人在大陆的驾驶证年审过期了,可不可以延迟办理?
  • 原文地址:https://www.cnblogs.com/yangcaogui/p/3587567.html
Copyright © 2020-2023  润新知