• 通过监听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

  • 相关阅读:
    第一次点击button, view视图出现;第二次点击button,view视图消失
    快速破解ps方法
    终端中出现While executing gem ... (Errno::EPERM) Operation not permitted
    隐藏顶部状态栏的方法
    python之dict和set
    python之循环
    python 之条件判断
    python 之列表和元组
    python 之字符串和编码
    人生第一次面试之旅
  • 原文地址:https://www.cnblogs.com/Yjianyong/p/2331040.html
Copyright © 2020-2023  润新知