学习之路三十九:新手学习 - Windows API
2014-03-09 10:30
225 查看
来到了新公司,一开始就要做个程序去获取另外一个程序里的数据,哇,挑战性很大。
经过两周的学习,终于搞定,主要还是对Windows API有了更多的了解。
文中所有的消息常量,API,结构体都整理出来了(还不是很全):Windows.zip
目录:
获取控件句柄
模拟键盘和鼠标
文本框赋值
操作DateTimePicker控件
操作TreeView控件
识别简单验证码
判断按钮状态
正文:
一丶怎么获取每个控件的句柄
第一种是使用FindWindow和FindWindowEx两个API结合使用,但太累太繁琐,不爽。
说实话第一次我是通过Spy++看我所需要的控件的顺序,然后循环几次获取这个控件的句柄,显然这种方式很麻烦。
第二种是在网上看到了另一种获取控件句柄的方式:
①每个控件都有唯一的ControlID
②通过Spy++查看每一个ControlID,并通过EnumChildWindows来循环每一个控件
代码如下:
二丶模拟键盘和鼠标
在一些特殊的情况下,没有办法发送消息通知控件触发单击事件(或其它事件),只能通过模拟键盘和鼠标来操作了。
关于Hook的学习请看 - 学习之路三十八:Hook(钩子)的学习
①mouse_event - 鼠标操作
②keybd_event - 触发键盘
API的定义:
关于鼠标API的调用:
三丶给文本框赋值
在Windows API我感觉比较重要的方法是SendMessage,几乎所有的发送指令都需要用到它,把它用好就成功了一大半了(瞎说的)。
其实通过Reflector查看.NET源码,发现内部方法中SendMessage有将近20个重载方法,很多,举其中一个列子:
四丶操作DatetimePicker控件
操作日期控件我查找资料搞了好久,原来它并不是仅仅发送一个消息就可以搞定的,我猜测大多数复杂控件要触发事件肯定不能用SendMessage就以为搞定了。
原来要想给控件赋值必须用到操作内存的方式,代码如下:
步骤:①根据句柄获取进程ID
②打开进程并获取进程句柄
③在进程中申请内存空间并返回申请的内存地址
④把数据写入到刚刚开辟的内存空间去
⑤发送消息通知日期控件更新数据
⑥释放内存
PS:感觉往C++方面靠近了,学起来真心不容易啊,难怪说C++入门困难,领会到了,o(∩_∩)o 。
五丶操作TreeView控件
说实话对于怎么触发TreeView的Node单击事件我还没有找到资料,希望会的朋友告诉我,我感激不尽。
首先获取TreeView控件的句柄是首要条件。
①获取根节点
②选中根节点
③获取指定节点句柄
④选中并展开节点
⑤寻找二级节点:注意消息常量的运用
⑥节点的单击事件
说实话我没有真正的通过发送消息来实现事件通知,只有通过模拟鼠标来操作的,希望懂得朋友教教我。
六丶识别简单验证码
图像识别这个领域高深莫测,不是那么容易搞得定的,前几天搞了一个对简单验证码的识别。
基本步骤是:
①获取图片对象
②进行灰度化处理
③阈值 - 也就是换成白底黑字的图片
④分割图片
⑤处理每一张分割的图片(获取黑色像素点)
★:因为我的验证相对比较简单(纯数字,只是加了一些颜色干扰),我采取了一种简洁方式,通过观察我发现每个数字占的像素点都是一致的,所以前期我都把每个数字占的像素点都算出来了。
从面前测试情况下来看正确率为100%。
代码如下(展不开,重新刷新下就可以了):
View Code
七丶判断按钮的状态
当我点击一个按钮去查询数据的时候,可能要花点时间,所以会把按钮的状态设置为不可用,那么Windows API是这样调用的:
就这么多了,也只是把用到的记录了一下,没用到也不去学它,:-)。
以同步至:个人文章目录索引
经过两周的学习,终于搞定,主要还是对Windows API有了更多的了解。
文中所有的消息常量,API,结构体都整理出来了(还不是很全):Windows.zip
目录:
获取控件句柄
模拟键盘和鼠标
文本框赋值
操作DateTimePicker控件
操作TreeView控件
识别简单验证码
判断按钮状态
正文:
一丶怎么获取每个控件的句柄
第一种是使用FindWindow和FindWindowEx两个API结合使用,但太累太繁琐,不爽。
说实话第一次我是通过Spy++看我所需要的控件的顺序,然后循环几次获取这个控件的句柄,显然这种方式很麻烦。
第二种是在网上看到了另一种获取控件句柄的方式:
①每个控件都有唯一的ControlID
②通过Spy++查看每一个ControlID,并通过EnumChildWindows来循环每一个控件
代码如下:
public class ExportForm : BaseForm { private string _userID = string.Empty; private IntPtr _cancelButtonHandle = IntPtr.Zero; private readonly int _cancelButtonControlID = Convert.ToInt32("00000002", 16); //通过Spy++获取你想要的ControlID private readonly int _confirmButtonControlID = Convert.ToInt32("00000001", 16); public ExportForm(string userID) { this._userID = userID; } public override sealed void GetAllHandles() { base.LoadFormHandle(null, CITICConfigInfo.ExportFormName); WindowsAPI.EnumChildWindows(base.FormHandle, (handle, param) => //这个API是循环窗体中的所有控件 { int flagControlID = WindowsAPI.GetWindowLong(handle, WindowsConst.GWL_ID); //通过句柄获取ControlID if (flagControlID == this._cancelButtonControlID) { this._cancelButtonHandle = handle; } return true; }, 0); } }
二丶模拟键盘和鼠标
在一些特殊的情况下,没有办法发送消息通知控件触发单击事件(或其它事件),只能通过模拟键盘和鼠标来操作了。
关于Hook的学习请看 - 学习之路三十八:Hook(钩子)的学习
①mouse_event - 鼠标操作
②keybd_event - 触发键盘
API的定义:
/// <summary> /// 键盘操作指令 /// </summary> /// <param name="bVk">键盘指令:Enter,F1等键盘按钮,使用Keys枚举即可</param> /// <param name="bScan">默认都为 - 0</param> /// <param name="dwFlags">默认都为 - 0</param> /// <param name="dwExtraInfo">默认都为 - 0</param> [DllImport("user32.dll")] public static extern void keybd_event(Byte bVk, Byte bScan, Int32 dwFlags, Int32 dwExtraInfo); /// <summary> /// 设置鼠标操作指令 /// </summary> /// <param name="flag">指令类型:单击,移动,双击</param> /// <param name="x">X坐标的位置</param> /// <param name="y">Y坐标的位置</param> /// <param name="cButtons">默认都为 - 0</param> /// <param name="dwExtraInfo">默认都为 - 0</param> [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern void mouse_event(int flag, int x, int y, int cButtons, int dwExtraInfo);
关于鼠标API的调用:
Rectangle rectangle; WindowsAPI.GetWindowRect(this._treeViewHandle, out rectangle); WindowsAPI.SetCursorPos(rectangle.Left + 55, rectangle.Top + 220); WindowsAPI.mouse_event(WindowsConst.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0); //第一个参数为消息指令 WindowsAPI.mouse_event(WindowsConst.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
三丶给文本框赋值
在Windows API我感觉比较重要的方法是SendMessage,几乎所有的发送指令都需要用到它,把它用好就成功了一大半了(瞎说的)。
其实通过Reflector查看.NET源码,发现内部方法中SendMessage有将近20个重载方法,很多,举其中一个列子:
/// <summary> /// 给句柄发送指令消息,等待消息处理完成 /// </summary> /// <param name="handle">指定句柄</param> /// <param name="message">消息指令:如click</param> /// <param name="wParam">默认都为 - 0</param> /// <param name="lParam">默认都为 - 0</param> /// <returns>返回的结果</returns> [DllImport("user32.dll")] public static extern UInt32 SendMessage(IntPtr handle, UInt32 message, UInt32 wParam, UInt32 lParam); //调用方式 WindowsAPI.SendMessage(this._passwordHandle, WindowsConst.WM_SETTEXT, 0, "这是我发送的消息"); //最后一个参数是给文本框赋值内容
四丶操作DatetimePicker控件
操作日期控件我查找资料搞了好久,原来它并不是仅仅发送一个消息就可以搞定的,我猜测大多数复杂控件要触发事件肯定不能用SendMessage就以为搞定了。
原来要想给控件赋值必须用到操作内存的方式,代码如下:
步骤:①根据句柄获取进程ID
②打开进程并获取进程句柄
③在进程中申请内存空间并返回申请的内存地址
④把数据写入到刚刚开辟的内存空间去
⑤发送消息通知日期控件更新数据
⑥释放内存
private void OperationDateTimePicker() { SYSTEMTIME time = new SYSTEMTIME { wYear = 1990, wMonth = 10, wDay = 10 }; int objSize = Marshal.SizeOf(typeof(SYSTEMTIME)); byte[] buffer = new byte[objSize]; IntPtr flagHandle = Marshal.AllocHGlobal(objSize); Marshal.StructureToPtr(time, flagHandle, true); Marshal.Copy(flagHandle, buffer, 0, objSize); Marshal.FreeHGlobal(flagHandle); //①获取远程进程ID int processID = default(int); WindowsAPI.GetWindowThreadProcessId(this._startTimeHandle, ref processID); //②获取远程进程句柄 IntPtr processHandle = WindowsAPI.OpenProcess(WindowsConst.PROCESS_ALL_ACCESS, false, processID); //③在远程进程申请内存空间并返回内存地址 IntPtr memoryAddress = WindowsAPI.VirtualAllocEx(processHandle, IntPtr.Zero, objSize, WindowsConst.MEM_COMMIT, WindowsConst.PAGE_READWRITE); //④把数据写入上一步申请的内存空间 WindowsAPI.WriteProcessMemory(processHandle, memoryAddress, buffer, buffer.Length, 0); //⑤发送消息给句柄叫它更新数据 WindowsAPI.SendMessage(this._startTimeHandle, WindowsConst.DTM_SETSYSTEMTIME, WindowsConst.GDT_VALID, memoryAddress); //⑥释放内存并关闭句柄 WindowsAPI.VirtualFreeEx(processHandle, memoryAddress, objSize, WindowsConst.MEM_RELEASE); WindowsAPI.CloseHandle(processHandle); }
PS:感觉往C++方面靠近了,学起来真心不容易啊,难怪说C++入门困难,领会到了,o(∩_∩)o 。
五丶操作TreeView控件
说实话对于怎么触发TreeView的Node单击事件我还没有找到资料,希望会的朋友告诉我,我感激不尽。
首先获取TreeView控件的句柄是首要条件。
①获取根节点
//①获取根节点 int rootNodeNum = WindowsAPI.SendMessage(this._treeViewHandle, WindowsConst.TVM_GETNEXTITEM, WindowsConst.TVGN_ROOT, 0); IntPtr rootNodeHandle = new IntPtr(rootNodeNum);
②选中根节点
//②选中根节点 WindowsAPI.SendMessage(this._treeViewHandle, WindowsConst.TVM_SELECTITEM, WindowsConst.TVGN_CARET, rootNodeHandle);
③获取指定节点句柄
//③遍历所有一级节点,获取我想要的节点句柄 IntPtr selectNodeHandle = rootNodeHandle; for (int num = 1; num <= 6; num++) //记住节点的顺序,我想要的节点位置在第六个上 { int flagNodeNum = WindowsAPI.SendMessage(this._treeViewHandle, WindowsConst.TVM_GETNEXTITEM, WindowsConst.TVGN_NEXT, selectNodeHandle); selectNodeHandle = new IntPtr(flagNodeNum); }
④选中并展开节点
//④展开节点 WindowsAPI.SendMessage(this._treeViewHandle, WindowsConst.TVM_SELECTITEM, WindowsConst.TVGN_CARET, selectNodeHandle); //最后一个参数为第三步获取的节点句柄 WindowsAPI.SendMessage(this._treeViewHandle, WindowsConst.TVM_EXPAND, WindowsConst.TVE_EXPAND, selectNodeHandle);
⑤寻找二级节点:注意消息常量的运用
//⑤继续循环当前节点,获取我想要的二级节点 IntPtr childrenNodeHandle = selectNodeHandle; for (int num = 1; num <= 5; num++) { int flagNode = WindowsAPI.SendMessage(this._treeViewHandle, WindowsConst.TVM_GETNEXTITEM, WindowsConst.TVGN_CHILD, childrenNodeHandle); childrenNodeHandle = new IntPtr(flagNode); }
⑥节点的单击事件
说实话我没有真正的通过发送消息来实现事件通知,只有通过模拟鼠标来操作的,希望懂得朋友教教我。
//⑥单击节点--模拟鼠标单击 Rectangle rectangle; WindowsAPI.GetWindowRect(this._treeViewHandle, out rectangle); WindowsAPI.SetCursorPos(rectangle.Left + 55, rectangle.Top + 220); WindowsAPI.mouse_event(WindowsConst.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0); WindowsAPI.mouse_event(WindowsConst.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
六丶识别简单验证码
图像识别这个领域高深莫测,不是那么容易搞得定的,前几天搞了一个对简单验证码的识别。
基本步骤是:
①获取图片对象
②进行灰度化处理
③阈值 - 也就是换成白底黑字的图片
④分割图片
⑤处理每一张分割的图片(获取黑色像素点)
★:因为我的验证相对比较简单(纯数字,只是加了一些颜色干扰),我采取了一种简洁方式,通过观察我发现每个数字占的像素点都是一致的,所以前期我都把每个数字占的像素点都算出来了。
从面前测试情况下来看正确率为100%。
<add key="0" value="38"/> <add key="1" value="19"/> <add key="2" value="37"/> <add key="3" value="52"/> <add key="4" value="50"/> <add key="5" value="35"/> <add key="6" value="41"/> <add key="7" value="28"/> <add key="8" value="64"/> <add key="9" value="42"/>
代码如下(展不开,重新刷新下就可以了):
namespace BLL { public class IdentifyingCode { private string BlackFlag = "1"; private string WhiteFlag = "0"; private Dictionary<int, string> Values; public static readonly IdentifyingCode Instance = new IdentifyingCode(); private IdentifyingCode() { this.Values = this.LoadData(); } private Dictionary<int, string> LoadData() { Dictionary<int, string> values = new Dictionary<int, string>(10); for (int index = 0; index < 10; index++) { string value = index.ToString(CultureInfo.InvariantCulture); string key = ConfigurationManager.AppSettings[value]; values.Add(key.ToInt32(), value); } return values; } /// <summary> /// 获取验证码内容 /// </summary> /// <param name="filePath">图片路径</param> /// <returns>验证码值</returns> public string Check(string filePath) { return this.Check(new Bitmap(filePath)); } /// <summary> /// 获取验证码内容 /// </summary> /// <param name="bitmap">图片对象</param> /// <returns>验证码值</returns> public string Check(Bitmap bitmap) { using (bitmap) { this.Gray(bitmap); this.Threshold(bitmap, 190); List<TempSize> tempSizes = this.Carve(bitmap); return this.GetValue(bitmap, tempSizes.ToArray()); } } /// <summary> /// 灰度化 /// </summary> /// <param name="bitmap">图片对象</param> private void Gray(Bitmap bitmap) { for (int x = 0; x < bitmap.Width; x++) { for (int y = 0; y < bitmap.Height; y++) { int grayNumber = GetGrayNumber(bitmap.GetPixel(x, y)); bitmap.SetPixel(x, y, Color.FromArgb(grayNumber, grayNumber, grayNumber)); } } } /// <summary> /// 获取灰度化的临界点 /// </summary> /// <param name="color">每个像素的颜色对象</param> /// <returns>临界值</returns> private int GetGrayNumber(Color color) { return (int)(color.R * 0.3 + color.G * 0.59 + color.B * 0.11); } /// <summary> /// 阈值,也就是转换成白底黑字的图片 /// </summary> /// <param name="bitmap">图片对象</param> /// <param name="criticalValue">临界值</param> private void Threshold(Bitmap bitmap, int criticalValue) { for (int x = 0; x < bitmap.Width; x++) { for (int y = 0; y < bitmap.Height; y++) { Color color = bitmap.GetPixel(x, y); bitmap.SetPixel(x, y, color.R >= criticalValue ? Color.White : Color.Black); } } } /// <summary> /// 分割图片 /// </summary> /// <param name="originalBitmap">图片对象</param> /// <returns>每个图片的范围</returns> private List<TempSize> Carve(Bitmap originalBitmap) { string blackPointFlags = GetBlackPointFlags(originalBitmap); bool flag = true; int xStart = default(int); List<TempSize> tempSizes = new List<TempSize>(); for (int x = 0; x < originalBitmap.Width; x++) { if (blackPointFlags.Substring(x, 1) == BlackFlag) { if (flag) { flag = false; xStart = x; } if (x < originalBitmap.Width) { if (blackPointFlags.Substring(x + 1, 1) == WhiteFlag) { int xEnd = x; TempSize tempSize = new TempSize { XStart = xStart, XWidth = (xEnd - xStart) + 1 }; tempSizes.Add(tempSize); } } } else { flag = true; //重新开始 } } return tempSizes; } private string GetBlackPointFlags(Bitmap originalBitmap) { string everyColumnHasBlackPoints = string.Empty; for (int x = 0; x < originalBitmap.Width; x++) { for (int y = 0; y < originalBitmap.Height; y++) { if (originalBitmap.GetPixel(x, y).R == Color.Black.R) { everyColumnHasBlackPoints += "1"; break; } } if (everyColumnHasBlackPoints.Length != x + 1) { everyColumnHasBlackPoints += "0"; } } return everyColumnHasBlackPoints; } private string GetValue(Bitmap originalBitmap, TempSize[] tempSizes) { string result = string.Empty; for (int index = 0; index < tempSizes.Length; index++) { string pointValues = string.Empty; TempSize tempSize = tempSizes[index]; for (int x = tempSize.XStart; x < tempSize.XStart + tempSize.XWidth; x++) { for (int y = 0; y < originalBitmap.Height; y++) { var color = originalBitmap.GetPixel(x, y); pointValues += color.R == 0 ? "1" : "0"; } } int blackPointCount = pointValues.Count(p => p == '1'); if (this.Values.ContainsKey(blackPointCount)) { result += this.Values[blackPointCount]; } } return result; } } public struct TempSize { public int XStart; public int XWidth; } }
View Code
七丶判断按钮的状态
当我点击一个按钮去查询数据的时候,可能要花点时间,所以会把按钮的状态设置为不可用,那么Windows API是这样调用的:
//这边我需要检查“查询”按钮的状态,如果为灰色要等待,否则继续下去 bool selectButtonStatus = false; while (selectButtonStatus == false) { selectButtonStatus = WindowsAPI.IsWindowEnabled(this._selectButtonHandle); }
就这么多了,也只是把用到的记录了一下,没用到也不去学它,:-)。
以同步至:个人文章目录索引
相关文章推荐
- windows API 菜鸟学习之路(三)
- 新手学习java设计模式之路——工厂模式
- 转载 前端基础知识体系 一个新手的学习之路
- windows API 菜鸟学习之路(二)
- 新手学习PHP之路(1)
- Flex新手学习实践之路
- 新手学习,web编程入门(四)——编程之路中的苦与乐才刚开始品尝
- 新手程序员的学习之路
- OpenCV新手学习之路:写在开篇
- OpenCV新手学习之路(一):环境的搭建(Win8.1 + VS2013 + OpenCV 2.4.8)
- 新手学习,web编程入门(三)——编程之路起步环境搭建
- windows API 菜鸟学习之路(四)
- linux下C语言,我的新手学习之路。
- Windows API学习之路一:程序入口、LPSTR与句柄
- 新手微擎(微赞)学习之路一
- 新手学习之路(一)————堆排序
- 新手学习之路(二)————计数排序到基数排序
- CCLayerColor和CCClippingNode组合新手引导功能---cocos2d-x学习之路[4]
- git-新手学习之路
- 新手学习Java之路