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

经过两周的学习,终于搞定,主要还是对Windows API有了更多的了解。

文中所有的消息常量,API,结构体都整理出来了(还不是很全):Windows.zip 

目录:

  1. 获取控件句柄
  2. 模拟键盘和鼠标
  3. 文本框赋值
  4. 操作DateTimePicker控件
  5. 操作TreeView控件
  6. 识别简单验证码
  7. 判断按钮状态

正文:

一丶怎么获取每个控件的句柄

  第一种是使用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("", ); //通过Spy++获取你想要的ControlID
private readonly int _confirmButtonControlID = Convert.ToInt32("", );

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;
}, );
}
}

二丶模拟键盘和鼠标

  在一些特殊的情况下,没有办法发送消息通知控件触发单击事件(或其它事件),只能通过模拟键盘和鼠标来操作了。

  关于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 + , rectangle.Top + );
WindowsAPI.mouse_event(WindowsConst.MOUSEEVENTF_LEFTDOWN, , , , ); //第一个参数为消息指令
WindowsAPI.mouse_event(WindowsConst.MOUSEEVENTF_LEFTUP, , , , );

三丶给文本框赋值

  在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, , "这是我发送的消息"); //最后一个参数是给文本框赋值内容

四丶操作DatetimePicker控件

  操作日期控件我查找资料搞了好久,原来它并不是仅仅发送一个消息就可以搞定的,我猜测大多数复杂控件要触发事件肯定不能用SendMessage就以为搞定了。

  原来要想给控件赋值必须用到操作内存的方式,代码如下:

  步骤:①根据句柄获取进程ID

     ②打开进程并获取进程句柄

     ③在进程中申请内存空间并返回申请的内存地址

       ④把数据写入到刚刚开辟的内存空间去

     ⑤发送消息通知日期控件更新数据

     ⑥释放内存

         private void OperationDateTimePicker()
{
SYSTEMTIME time = new SYSTEMTIME { wYear = , wMonth = , wDay = }; 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, , 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, );
//⑤发送消息给句柄叫它更新数据
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, );
IntPtr rootNodeHandle = new IntPtr(rootNodeNum);

  ②选中根节点

     //②选中根节点
WindowsAPI.SendMessage(this._treeViewHandle, WindowsConst.TVM_SELECTITEM, WindowsConst.TVGN_CARET, rootNodeHandle);

   ③获取指定节点句柄

     //③遍历所有一级节点,获取我想要的节点句柄
IntPtr selectNodeHandle = rootNodeHandle;
for (int num = ; num <= ; 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 = ; num <= ; 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 + , rectangle.Top + );
WindowsAPI.mouse_event(WindowsConst.MOUSEEVENTF_LEFTDOWN, , , , );
WindowsAPI.mouse_event(WindowsConst.MOUSEEVENTF_LEFTUP, , , , );

 六丶识别简单验证码

  图像识别这个领域高深莫测,不是那么容易搞得定的,前几天搞了一个对简单验证码的识别。

  基本步骤是:

  ①获取图片对象

  ②进行灰度化处理

  ③阈值 - 也就是换成白底黑字的图片

  ④分割图片

  ⑤处理每一张分割的图片(获取黑色像素点)

  ★:因为我的验证相对比较简单(纯数字,只是加了一些颜色干扰),我采取了一种简洁方式,通过观察我发现每个数字占的像素点都是一致的,所以前期我都把每个数字占的像素点都算出来了。

  从面前测试情况下来看正确率为100%。

     <add key="" value=""/>
<add key="" value=""/>
<add key="" value=""/>
<add key="" value=""/>
<add key="" value=""/>
<add key="" value=""/>
<add key="" value=""/>
<add key="" value=""/>
<add key="" value=""/>
<add key="" value=""/>

  代码如下(展不开,重新刷新下就可以了):

 namespace BLL
{
public class IdentifyingCode
{
private string BlackFlag = "";
private string WhiteFlag = "";
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>();
for (int index = ; index < ; 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, );
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 = ; x < bitmap.Width; x++)
{
for (int y = ; 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 = ; x < bitmap.Width; x++)
{
for (int y = ; 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 = ; x < originalBitmap.Width; x++)
{
if (blackPointFlags.Substring(x, ) == BlackFlag)
{
if (flag)
{
flag = false;
xStart = x;
}
if (x < originalBitmap.Width)
{
if (blackPointFlags.Substring(x + , ) == WhiteFlag)
{
int xEnd = x;
TempSize tempSize = new TempSize
{
XStart = xStart,
XWidth = (xEnd - xStart) +
};
tempSizes.Add(tempSize);
}
}
}
else
{
flag = true; //重新开始
}
}
return tempSizes;
} private string GetBlackPointFlags(Bitmap originalBitmap)
{
string everyColumnHasBlackPoints = string.Empty; for (int x = ; x < originalBitmap.Width; x++)
{
for (int y = ; y < originalBitmap.Height; y++)
{
if (originalBitmap.GetPixel(x, y).R == Color.Black.R)
{
everyColumnHasBlackPoints += "";
break;
}
}
if (everyColumnHasBlackPoints.Length != x + )
{
everyColumnHasBlackPoints += "";
}
}
return everyColumnHasBlackPoints;
} private string GetValue(Bitmap originalBitmap, TempSize[] tempSizes)
{
string result = string.Empty;
for (int index = ; 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 = ; y < originalBitmap.Height; y++)
{
var color = originalBitmap.GetPixel(x, y);
pointValues += color.R == ? "" : "";
}
} int blackPointCount = pointValues.Count(p => p == '');
if (this.Values.ContainsKey(blackPointCount))
{
result += this.Values[blackPointCount];
}
}
return result;
}
} public struct TempSize
{
public int XStart;
public int XWidth;
}
}

七丶判断按钮的状态

  当我点击一个按钮去查询数据的时候,可能要花点时间,所以会把按钮的状态设置为不可用,那么Windows API是这样调用的:

     //这边我需要检查“查询”按钮的状态,如果为灰色要等待,否则继续下去
bool selectButtonStatus = false;
while (selectButtonStatus == false)
{
selectButtonStatus = WindowsAPI.IsWindowEnabled(this._selectButtonHandle);
}

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

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

学习之路三十九:新手学习 - Windows API的更多相关文章

  1. FastAPI 学习之路(十九)处理错误

    系列文章: FastAPI 学习之路(一)fastapi--高性能web开发框架 FastAPI 学习之路(二) FastAPI 学习之路(三) FastAPI 学习之路(四) FastAPI 学习之 ...

  2. Dynamic CRM 2013学习笔记(三十九)流程2 - 业务流程(Business Process Flows)用法详解

    业务流程(Business Process Flows)是CRM 2013 里一个新的流程,它提供了可视化的流程表现.业务人员创建有效.流线型的业务流程让最终用户知道当前在哪.下一步要做什么,用户可以 ...

  3. 学习之路三十二:VS调试的简单技巧

    这段时间园子里讲了一些关于VS的快捷键以及一些配置技巧,挺好的,大家一起学习,一起进步. 这段时间重点看了一下关于VS调试技巧方面的书,在此记录一下学习的内容吧,主要还是一些比较浅显的知识. 1. 调 ...

  4. 【WPF学习】第三十九章 理解形状

    在WPF用户界面中,绘制2D图形内容的最简单方法是使用形状(shape)——专门用于表示简单的直线.椭圆.矩形以及多变形的一些类.从技术角度看,形状就是所谓的绘图图元(primitive).可组合这些 ...

  5. 学习之路三十五:Android和WCF通信 - 大数据压缩后传输

    最近一直在优化项目的性能,就在前几天找到了一些资料,终于有方案了,那就是压缩数据. 一丶前端和后端的压缩和解压缩流程 二丶优点和缺点 优点:①字符串的压缩率能够达到70%-80%左右 ②字符串数量更少 ...

  6. Spark学习之路 (十九)SparkSQL的自定义函数UDF

    在Spark中,也支持Hive中的自定义函数.自定义函数大致可以分为三种: UDF(User-Defined-Function),即最基本的自定义函数,类似to_char,to_date等 UDAF( ...

  7. Hadoop学习之路(十九)MapReduce框架排序

    流量统计项目案例 样本示例 需求 1. 统计每一个用户(手机号)所耗费的总上行流量.总下行流量,总流量 2. 得出上题结果的基础之上再加一个需求:将统计结果按照总流量倒序排序 3. 将流量汇总统计结果 ...

  8. Spark学习之路 (十九)SparkSQL的自定义函数UDF[转]

    在Spark中,也支持Hive中的自定义函数.自定义函数大致可以分为三种: UDF(User-Defined-Function),即最基本的自定义函数,类似to_char,to_date等 UDAF( ...

  9. 学习之路三十八:Hook(钩子)的学习

    好久没写文章了,还记得年前面试了一家公司,为了检测一下我的学习能力,给了我一个任务,做一个自动登录并自动操作菜单的程序. 花了几天的时间研究了Hook以及使用WindowsAPI操作程序的知识,现在记 ...

随机推荐

  1. beetle 2.7海量消息广播测试

    由于client资源限制,只进行了300物体互动广播测试:物体活动频率是每秒20次,服务器每秒转发的消息量大概180W条. 转发消息结构: class Po : IMessage { public i ...

  2. 设计模式之美:Structural Patterns(结构型模式)

    结构型模式涉及到如何组合类和对象以获得更大的结构. 结构型类模式采用继承机制来组合接口实现. 结构型对象模式不是对接口和实现进行组合,而是描述了如何对一些对象进行组合,从而实现新功能的一些方法. 因为 ...

  3. C语言 栈 链式结构 实现

    一个C语言链式结构实现的栈 mStack (GCC编译). /** * @brief C语言实现的链式结构类型的栈 * @author wid * @date 2013-10-30 * * @note ...

  4. atitit.文件上传带进度条的实现原理and组件选型and最佳实践总结O7

    atitit.文件上传带进度条的实现原理and组件选型and最佳实践总结O7 1. 实现原理 1 2. 大的文件上传原理::使用applet 1 3. 新的bp 2 1. 性能提升---分割小文件上传 ...

  5. SHINY-SERVER R(sparkR)语言web解决方案 架设shiny服务器

    1. shiny server简介 shiny-server是一种可用把R 语言以web形式展示的服务,其实RStudio公司自己构建了R Shiny Application运行的平台(http:// ...

  6. CentOS 6.5 无网环境安装R及Rstudio的方法的方法

    在生产环节,一般是不联网的,下面介绍在无望环境如何安装R及R-studio 1.  安装CentOS for R语言的基础环境 1.1 libpng,X11,libjpeg等支持 yum -y ins ...

  7. android WebView控件显示网页

    有时需要app里面显示网页,而不调用其他浏览器浏览网页,那么这时就需要WebView控件.这个控件也是很强大的,放大,缩小,前进,后退网页都可以. 1.部分方法 //支持javascriptweb.g ...

  8. Android兼容包multidex的开发和构建方法

    在Android开发中,函数方法超过65k限制后,我们就常常会用到multidex分包解决,但是multidex的配置,对系统apk的构建.签名.打包复杂性大大的增加,严重的降低了构建效率.那这个问题 ...

  9. photoshop 快速切图

    发现一个详细好方法:http://blog.csdn.net/zhangxiaowei_/article/details/42143307 具体如下:

  10. [原创]推荐一款强大的.NET程序内存分析工具.NET Memory Profiler

    [原创]推荐一款强大的.NET程序内存分析工具.NET Memory Profiler 1 官方网站:http://memprofiler.com/2 下载地址:http://memprofiler. ...