通過監聽Windows消息實現控件的鍵盤和鼠標事件路由


      以前發表過《通過監聽Windows消息對復合控件進行整體控制(C#)一 》兩篇,講述了通過FrameWork框架提供的技術監聽Windows消息來實現事件的路由,但部分實現並不是很好,而且有部分功能並不能很好解決控件的事件,此篇通過對原方法進行改寫,有些實現通過調用Windows API輔助解決,基本上解決了控件的鍵盤和鼠標事件的路由。(JS和WPF有事件路由的功能)

      實現 IMessageFilter 接口 和 注冊控件的鼠標和鍵盤事件等對外接口請參考《通過監聽Windows消息對復合控件進行整體控制(C#)一 》,內部的實現通過使用Windows API 使代碼更嚴謹和清晰。

      使用到的部分API方法

 

View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace LC.Windows.MessageListen
{
     class Win32API
    {
         // static public long Count;
         #region  常量及開源

         public  const  int WM_KEYDOWN =  0x100;
         public  const  int WM_SYSKEYDOWN =  0x104;
         public  const  int WM_KEYUP =  0x101;
         public  const  int WM_SYSKEYUP =  0x105;
         public  const  int WM_MOUSEMOVE =  0x200;
         public  const  int WM_MOUSEHOVER =  0x2a1;
         public  const  int WM_MOUSELEAVE =  0x2a3;
         public  const  int WM_LBUTTONDOWN =  0x201;
         public  const  int WM_LBUTTONUP =  0x202;
         public  const  int WM_LBUTTONDBLCLK =  0x203;
         public  const  int WM_RBUTTONDOWN =  0x204;
         public  const  int WM_RBUTTONUP =  0x205;
         public  const  int WM_RBUTTONDBLCLK =  0x206;
         public  const  int WM_NCMOUSEMOVE =  0xa0;
         public  const  int WM_NCLBUTTONDOWN =  0xa1;
         public  const  int WM_NCLBUTTONUP =  0xa2;
         public  const  int WM_MOUSEWHEEL =  0x20a;
         public  const  int WM_PAINT =  0xf;
         public  const  int WM_TIMER =  0x113;

         // ----------開源代碼----------------------
         int zDelta;

         public  const  int MK_LBUTTON =  0x0001;
         public  const  int MK_RBUTTON =  0x0002;
         public  const  int MK_SHIFT =  0x0004;
         public  const  int MK_CONTROL =  0x0008;
         public  const  int MK_MBUTTON =  0x0010;
         public  const  int MK_XBUTTON1 =  0x0020;
         public  const  int MK_XBUTTON2 =  0x0040;
         public  static  int GetXLParam( int lparam) {  return LowWord(lparam); }
         public  static  int GetYLParam( int lparam) {  return HighWord(lparam); }
         public  static  int LowWord( int word) {  return word &  0xFFFF; }
         public  static  int HighWord( int word) {  return word >>  16; }
         public  static  int GetWheelDeltaWParam( int wparam) {  return HighWord(wparam); }
         public  static MouseButtons GetMouseButtonWParam( int wparam)
        {
             int mask = LowWord(wparam);

             if ((mask & MK_LBUTTON) == MK_LBUTTON)  return MouseButtons.Left;
             if ((mask & MK_RBUTTON) == MK_RBUTTON)  return MouseButtons.Right;
             if ((mask & MK_MBUTTON) == MK_MBUTTON)  return MouseButtons.Middle;
             if ((mask & MK_XBUTTON1) == MK_XBUTTON1)  return MouseButtons.XButton1;
             if ((mask & MK_XBUTTON2) == MK_XBUTTON2)  return MouseButtons.XButton2;

             return MouseButtons.None;
        }


         public MouseButtons GetMouseUpMouseButtons( int msg)
        {
             if (msg == WM_LBUTTONUP)
                 return MouseButtons.Left;
             else  if (msg == WM_RBUTTONUP)
                 return MouseButtons.Right;
             else
                 return MouseButtons.None;
        }



         public  const  int CWP_ALL =  0x0000;
         public  const  int CWP_SKIPINVISIBLE =  0x0001;
         public  const  int CWP_SKIPDISABLED =  0x0002;
         public  const  int CWP_SKIPTRANSPARENT =  0x0004;

         public  const  uint TME_HOVER =  0x00000001;
         public  const  uint TME_LEAVE =  0x00000002;
         public  const  uint TME_QUERY =  0x40000000;
         public  const  uint TME_CANCEL =  0x80000000;

        [System.Runtime.InteropServices.DllImport( " user32.dll ")]
         public  static  extern  int ShowCursor( bool isShow);
        
        [System.Runtime.InteropServices.DllImport( " user32.dll ")]
         public  static  extern  int GetCapture();

        [System.Runtime.InteropServices.DllImport( " user32.dll ")]
         public  static  extern  int SetCapture(IntPtr hWnd);

        [System.Runtime.InteropServices.DllImport( " user32.dll ")]
         public  static  extern  bool ReleaseCapture();

        [System.Runtime.InteropServices.DllImport( " user32.dll ")]
         public  static  extern  bool GetCursorPos( ref POINT pos);     // 如果POINT是class 就不能加ref

        
// 取出客戶端的矩形,相對於控件自身。
        [System.Runtime.InteropServices.DllImport( " user32.dll ")]
         private  static  extern  bool GetClientRect( int hwnd, [In, Out] ref RECT rect);


        [System.Runtime.InteropServices.DllImport( " user32.dll ")]
         public  static  extern  bool PtInRect( ref RECT rect, POINT pos);

        [System.Runtime.InteropServices.DllImport( " user32.dll ")]
         public  static  extern  int ChildWindowFromPointEx( int hwnd, POINT pos,  uint un);

        [System.Runtime.InteropServices.DllImport( " user32.dll ")]
         public  static  extern  bool ClientToScreen( int hwnd,  ref POINT pos);

        [System.Runtime.InteropServices.DllImport( " user32.dll ")]
         public  static  extern  bool ScreenToClient( int hwnd,  ref POINT pos);

        [System.Runtime.InteropServices.DllImport( " user32.dll ")]
         public  static  extern  bool IsChild( int hWndParent,  int hWnd);

        [System.Runtime.InteropServices.DllImport( " user32.dll ")]
         public  static  extern  int MapWindowPoints( int hWndFrom,  int hWndTo,  ref POINT point,  int count);

        [System.Runtime.InteropServices.DllImport( " user32.dll ")]
         public  static  extern  int WindowFromPoint(POINT point);

        [System.Runtime.InteropServices.DllImport( " user32.dll ")]
         public  static  extern  int TrackMouseEvent( ref MouseTrackEvent trackEvent);

         // 獲取窗口標題
        [DllImport( " user32 ", SetLastError =  true)]
         public  static  extern  int GetWindowText(
         int hWnd,  // 窗口句柄
        StringBuilder lpString,  // 標題
         int nMaxCount  // 最大值
        );

         // 獲取類的名字
        [DllImport( " user32.dll ")]
         private  static  extern  int GetClassName(
         int hWnd,  // 句柄
        StringBuilder lpString,  // 類名
         int nMaxCount  // 最大值
        );

         /// /根據坐標獲取窗口句柄
         // [DllImport("user32")]
        
// private static extern IntPtr WindowFromPoint(
        
// POINT Point  // 坐標
        
// );

         #endregion


    }

    [StructLayout(LayoutKind.Sequential)]
     public  struct POINT
    {
         public  int X;
         public  int Y;
         public POINT( int x,  int y)
        {
             this.X = x;
             this.Y = y;
        }
    }

    [StructLayout(LayoutKind.Sequential)]
     public  struct RECT
    {
         public  int left;
         public  int top;
         public  int right;
         public  int bottom;
    }

    [StructLayout(LayoutKind.Sequential)]
     public  struct MouseTrackEvent
    {
         public  uint cbSize;
         public  uint dwFlags;
         public  int hwndTrack;
         public  uint dwHoverTime;
    }


}

 

注冊需要監聽的控件的事件,並對控件集合進行排序,根據包含關系由子到父進行排序

 

View Code
///   <summary>
        
///  注冊一個監聽的控件。
        
///   </summary>
        
///   <param name="wrapper"></param>
         public  void Regist(RegistControlWrapper wrapper)
        {
             lock (m_lckObj)
            {
                m_RegistControls[wrapper.Handle] = wrapper;
                 // 對集合進行包含性排序,即把子的排在前面
                
// 對集合進行包含性排序,即把子的排在前面
                m_KeyValuePairControls.Clear();
                 if (m_RegistControls.Count >=  2)     // 大於2就進行排序。
                    m_KeyValuePairControls = m_RegistControls.OrderBy(p => p,  new ContainSubCtrlComparer<KeyValuePair< int, RegistControlWrapper>>()).ToList();
                 else  if (m_RegistControls.Count ==  1)
                    m_KeyValuePairControls =  new List<KeyValuePair< int, RegistControlWrapper>>(m_RegistControls);

                 if (m_RegistControls.Count ==  1)
                {
                    Application.AddMessageFilter( this);
                }
            }
        }

 

在處理鼠標事件中,最麻煩的是鼠標進入事件和離開事件,由於IMessageFilter對進程外的其他進程的消息是監聽不了的,而鼠標離開事件並不能根據WM_MOUSELEAVE 消息進行即時的處理,因此,必需更好的使用API才能實現。

使用GetCursorPos獲取當前光標的位置,然后通過WindowFromPoint獲取光標對應的控件的句柄,再由IsChild判斷句柄是否在訂閱的控件里來決定鼠標是否完全離開該控件,對於由於進入子控件而產生的WM_MOUSELEAVE 消息,還得在合適的時機調用TrackMouseEvent,以使該控件下次能再次發出WM_MOUSELEAVE 消息。

 

鼠標離開的消息處理方法
   ///   <summary>
        
///  處理鼠標離開事件。
        
///   </summary>
        
///   <param name="m"></param>
        
///   <param name="isCancel"></param>
         private  void ProcessMouseLeave( ref Message m,  ref  bool cancel)
        {
             /// /如果是本集成的子控件導致頂層控件產生MouseLeave事件的,將屏閉該消息。

             int handle = m.HWnd.ToInt32();
             if (GetTopParenRegistControl(handle) >  0)
            {
                 // 獲取當前光標的位置下的控件是否為該控件的子
                POINT pt =  new POINT();
                Win32API.GetCursorPos( ref pt);        // 屏幕

                
// 獲取當前屏幕點下的控件
                 int newHandle = Win32API.WindowFromPoint(pt);

                 if (newHandle == m_CacheEntry.ParentHandle || Win32API.IsChild(m_CacheEntry.ParentHandle, newHandle))
                {
                     if (m_CacheEntry.CurrentHandle == m_CacheEntry.ParentHandle)
                    {
                        cancel =  true;       // 不做離開事件。如要下次再發出WM_MOUSELEAVE消息,得執行TrackMouseEvent方法(每調用些方法一次,才發出一次WM_MOUSELEAVE消息。)
                        m_CacheEntry.ControlWrapper.IsTrackLeave =  false;
                    }
                }
                 else
                {
                     // 真正的退出

                    
// 不做嵌套的控件的退出,覺得沒有意義
                    
// SetLeaveFlags(ref cancel);
                    
// RaiseAllMouseLeaveEvent(m_CacheEntry.CurrentHandle);

                     if (m_CacheEntry.CurrentHandle == m_CacheEntry.ParentHandle)
                    {
                        m_CacheEntry.ControlWrapper.IsTrackLeave =  false;
                    }
                     else
                        RaiseMouseLeaveEvent(m_CacheEntry.ParentHandle);          // 做多處離去事件,由里往外, 不知進入的是否要多處的需求(由外往里)?

                    m_CacheEntry.CurrentHandle =  0;
                    m_CacheEntry.ParentHandle =  0;
                }


             }

        }

 

在這個版本中,嚴謹度和清晰度大大提高,完全解決鼠標離開的消息,但調試中發現鼠標進入事件有時多執行一次,考慮到實現開發中多執行一次進入事件,影響不大,這個小Bug留真正有需要解決的讀者去完成,下面的完整的代碼,僅供學習。

/Files/Yjianyong/MessageListener.rar

 


免責聲明!

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



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