注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Hao的博客

I'm on my way……

 
 
 

日志

 
 
 
 

C# ColorRecognition小工具实现【下】  

2010-12-28 21:57:09|  分类: 小试牛刀 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

      上一篇文章中,已经解决了如何获取指定坐标点的颜色值,但是当鼠标离开主窗体时,程序就无法获知鼠标的位置了。正如之前所说,要捕获到鼠标在整个屏幕上滑动的消息,就必须捕获并处理系统的WM_MOUSEMOVE消息,这里将使用系统钩子实现该功能。
      简单来讲,钩子(HOOK)就是用于监听系统消息,并给出如何处理系统消息(响应、忽略、截断等)的一个函数。在C++中,可以使用SetWindowsHookEx来安装一个钩子,多个钩子将形成一条链,如果钩子函数中调用了CallNextHookEx,则捕获的系统消息将会继续传递给钩子链中的下一个钩子函数,当要移除一个钩子时,可以调用UnhookWindowsHookEx函数,上述三个函数均在user32.dll中。函数原型如下:
      HHOOK SetWindowsHookEx(int idHook, HOOKPROC lpfn,
                                                      HINSTANCE hMod, DWORD dwThreadId);
      LRESULT CallNextHookEx(HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam);
      BOOL UnhookWindowsHookEx(HHOOK hhk);

我将实现一个Hook类,以封装钩子的具体实现,并对外提供一个事件数据成员MouseMove,使其对外看起来和普通控件的事件没有任何区别,之后就不需要再处理窗口及控件的MouseMove事件了,而只需处理Hook类的MouseMove事件。主窗体内的部分代码如下:
      private Hook hook = new Hook(/*参数指定了hook捕获何种消息,以及是否为全局钩子*/);
      public Form1()
      {
          InitializeComponent();
          hook.MouseMove += hook_MouseMove;   //添加Hook类的MouseMove事件
      }

      private void hook_MouseMove(object sender, MouseEventArgs e)   //MouseMove事件处理程序
      {   //以下代码的实现与上篇博文中
Form1_MouseMove的实现一致,详见【上】
          Point pt = Control.MousePosition;
          Color color = /*CustomDLL.SystemUtility.*/Screen.GetColor(pt.X, pt.Y);
          colorTextBox.Text = color.ToArgb().ToString("X");
          sampleTextBox.BackColor = color;
      }

有了想要实现的效果后,下面就来实现Hook类。SetWindowsHookEx的第一个参数表示了钩子捕获何种类型,所以就先定义一个枚举类型,表示消息的类型,类型的值可以通过查阅MSDN得到,在这里只关心鼠标消息。代码如下:
      public enum HookType
      {
          //这里省略了其他的消息类型,只关心WH_MOUSE_LL
          WH_MOUSE_LL = 14
      }

SetWindowsHookEx的第二个参数指定了钩子处理函数,参照C++钩子处理函数的原型,定义一个C#的代理HookProcHandler来代替HOOKPROC:
      private delegate IntPtr HookProcHandler(int nCode, int wParam, IntPtr lParam);

SetWIndowsHookEx的第三个参数指定了钩子处理函数所在的DLL句柄。我将Hook类放到了一个DLL文件中,可以通过Assembly.GetExecutingAssembly().GetModules()[0]来得到所在的模块实例,然后将其作为Marshal.GetHINSTANCE的参数来得到相应的句柄。Hook类的完整代码如下:
      public class Hook
      {
          private IntPtr hHook=IntPtr.Zero;       //保存钩子句柄
          private HookProcHandler hookProc;   //钩子处理函数的代理

          public event MouseEventHandler MouseMove;  //定义事件数据成员MouseMove

         
[DllImport("user32.dll")]
          private static extern IntPtr SetWindowsHookEx(int idHook, HookProcHandler lpfn,
                                                                                      IntPtr hMod, int dwThreadId);
          [DllImport("user32.dll")]
          private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
                                                                                int wParam, IntPtr lParam);
          [DllImport("user32.dll")]
          private static extern bool UnhookWindowsHookEx(IntPtr hhk);

   
          public Hook() { }
          public Hook(HookType hookType, int threadId) {
              Start(hookType, threadId);
          }

          //根据钩子类型和线程ID安装一个钩子
          public void Start(HookType hookType, int threadId)
          {
             
//如果还未添加钩子,则根据消息类型选择对应的钩子处理函数,这里只关心WH_MOUSE_LL
              if (hHook == IntPtr.Zero)
              {
                  switch (hookType) { 
                      case HookType.WH_MOUSE_LL:
                          hookProc = MouseHookProc;
                          break;
                      default:
                          hookProc = HookProc;
                          break;
                  }
                  //安装钩子,并返回钩子句柄,第三个参数表示当前模块(DLL文件)句柄
                  hHook = SetWindowsHookEx((int)hookType, hookProc,
                      Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 
                      threadId);
                  if (hHook == IntPtr.Zero){ /*抛出安装钩子失败异常*/ }
              }
              else{
/*抛出已安装钩子异常*/ }
          }

    //移除钩子
          public void Stop() {
              if (hHook != IntPtr.Zero)
              {   //如果移除钩子失败
                  if (!UnhookWindowsHookEx(hHook)){ /*抛出钩子移除失败异常*/ }
                  else hHook = IntPtr.Zero;
              }
          }

    //默认钩子处理过程忽略消息,并将消息传递下去
          public IntPtr HookProc(int nCode, int wParam, IntPtr lParam) {
              return CallNextHookEx(hHook, nCode, wParam, lParam);
          }

    //处理鼠标消息的钩子处理函数
          public IntPtr MouseHookProc(int nCode, int wParam, IntPtr lParam) {
              if (nCode >= 0) {
                  switch (wParam) { 
                      case WM_MOUSEMOVE:  //WM_MOUSEMOVE = 0x200
                          if (MouseMove != null) {  //调用相应的MOUSEMOVE事件处理函数
                              MouseMove(this,new MouseEventArgs(MouseButtons.None, 0, 
                                                                      Cursor.Position.X, Cursor.Position.Y, 0));
                          }
                          break;
                   }
              }
              return CallNextHookEx(hHook, nCode, wParam, lParam); //将消息传递下去
          }
      }

这样就完成两个主要的部分,一个是获取给定坐标点屏幕的颜色,一个是捕获鼠标在屏幕上滑动的事件。起初我使用的是带参数的构造函数,在实例化时就安装了钩子,并提供了析构函数~Hook(){ Stop(); },但是在程序退出时UnhookWindowsHookEx函数发生了异常,原因是参数hHook非法,初步判断是由于C#中析构函数触发的时间无法预测,而应用程序在退出时,会销毁相关的资源(包括系统钩子), 如果该过程发生在析构函数之前,那么就可能抛出上述异常。去掉了析构函数后,虽然不会发生异常,但是会在关闭窗口时卡一会,即使在FormClosing事件中调用Hook.Stop卸载钩子也无济于事。为了解决这个问题,我添加了一个按钮,为了实现按钮被按下的效果,可以用RadioButton来代替,只要将Appearance属性设为Button即可。按钮被按下时安装钩子,并将this.Capture设置为true,使窗体捕获鼠标,而其他控件(包括最小化及关闭按钮所在的ControlBox)就无法在第一时间响应鼠标事件,给人的感觉好像是整个窗体失去了焦点。然后在窗体的MouseDown事件中判断按钮是否被按下,如果是则取消按下状态并卸载钩子。主窗口如下:

C ColorRecognition小工具实现【下】 - chhaj5236 - Hao的博客

后记:
      未添加RadioButton时,单击ControlBox(最小化及关闭按钮所在的控件)上的按钮,窗口会卡住一段时间,还不确定原因。


      
  评论这张
 
阅读(340)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017