首页/技术分享

Revit二次开发教程:Revit API Hook 之 拦截鼠标双击元素事件

发布于:2019-06-22 17:55:56
9593人 分享

HOOK(钩子,挂钩)是一种实现Windows平台下类似于中断的机制。HOOK机制允许应用程序拦截并处理Windows消息或指定事件,当指定的消息发出后,HOOK程序就可以在消息到达目标窗口之前将其捕获,从而得到对消息的控制权,进而可以对该消息进行处理或修改,加入我们所需的功能。钩子按使用范围分,可分为线程钩子和系统钩子,其中,系统钩子具有相当大的功能,几乎可以实现对所有Windows消息的拦截、处理和监控。这项技术涉及到两个重要的API,一个是SetWindowsHookEx,安装钩子;另一个是UnHookWindowsHookEx,卸载钩子。


本文使用的HOOK API技术,是指截获系统或进程对某个API函数的调用,使得API的执行流程转向我们指定的代码段,从而实现我们所需的功能。Windows下的每个进程均拥有自己的地址空间,并且进程只能调用其地址空间内的函数,因此HOOK API尤为关键的一步是,设法将自己的代码段注入到目标进程中,才能进一步实现对该进程调用的API进行拦截。然而微软并没有提供HOOK API的调用接口,这就需要开发者自己编程实现。

 一般来说,HOOK API由两个组成部分,即实现HOOK API的DLL文件,和启动注入的主调程序。本文采用HOOK API 技术对剪切板相关的API 函数进行拦截,从而实现对剪切板内容的监控功能,同样使用该技术实现进程防终止功能。其中DLL文件支持HOOK API的实现,而主调客户端程序将在初始化时把带有HOOK API功能的DLL随着鼠标钩子的加载注入到目标进程中,这里的鼠标钩子属于系统钩子。

下面介绍在Revit中,如何应用Hook对鼠标双击元素事件进行拦截。


第一步,先封装HookBase抽象类,因所有Hook的都具有注册、卸载逻辑,且注册、卸载大同小易。如下

public abstract class HookBase : IHook

    {

        private static Dictionary<int, IHook> m_Hooks;

        private IntPtr m_ProcessId;

        private int m_ThreadId;

        private HookType m_HookType;

        private HookProc m_HookProc; 

        protected internal int m_HookId; 

        static HookBase(){

            m_Hooks = new Dictionary<int, IHook>();

        } 

        private HookBase(HookType hookType){

            m_HookType = hookType;

            m_HookProc = HookProc;

        } 

        protected HookBase(IntPtr processId, HookType hookType):this(hookType){

            m_ProcessId = processId;

            if (m_ProcessId == IntPtr.Zero)

            {

                m_ProcessId = HookHelper.GetCurrentProcessId();

            }

        } 

        protected HookBase(int threadId, HookType hookType):this(hookType){

            m_ThreadId = threadId;

            if (m_ThreadId == 0)

            {

                m_ThreadId = HookHelper.GetCurrentThreadId();

            }

        } 

        public void Install(){

            if (m_ThreadId != 0)

            {

                m_HookId = HookHelper.SetWindowsHookEx(m_HookType, m_HookProc, IntPtr.Zero, m_ThreadId);

            }

            else

            {

                if (m_ProcessId == IntPtr.Zero)

                {

                    return;

                }

                m_HookId = HookHelper.SetWindowsHookEx(m_HookType, m_HookProc, m_ProcessId,0);

            }


            if (m_HookId == 0)

            {

                return;

            }


            if (!m_Hooks.ContainsKey(m_HookId))

            {

                m_Hooks.Add(m_HookId, this);

            }

        } 

        public void Uninstall()

        {

            if (m_HookId == 0)

            {

                return;

            }


            var flag = HookHelper.UnhookWindowsHookEx(m_HookId);

            if (flag)

            {

                if (m_Hooks.Remove(m_HookId))

                {

                    m_HookId = 0;

                }

            }

        } 

        protected abstract int HookProc(int nCode, IntPtr wParam, IntPtr lParam);


第二步 ,因鼠标Hook分为线程鼠标Hook以及全局鼠标Hook两种,仅注册方式有点区别。为使用方便,将其封装为事件注册方式。如下

public abstract class MouseHookBase : HookBase

    {

        protected MouseHookBase(IntPtr processId)

            : base(processId, HookType.WH_MOUSE_LL)

        {


        }


        protected MouseHookBase(int threadId)

            : base(threadId, HookType.WH_MOUSE)

        {


        }

        public event HookHandler<MouseEventArgs> MouseDoubleClick;

        public event HookHandler<MouseEventArgs> MouseMove;

        public event HookHandler<MouseEventArgs> MouseDown;

        public event HookHandler<MouseEventArgs> MouseUp;


        protected override int HookProc(int nCode, IntPtr wParam, IntPtr lParam)

        {

            if (nCode < 0)

            {

                return HookHelper.CallNextHookEx(m_HookId, nCode, wParam, lParam);

            }


            var mouseMsg = (MouseMessage)wParam.ToInt32();

            var mouseHookStruct = lParam.ToStruct<MOUSEHOOKSTRUCT>();


            var button = this.GetMouseButtons(mouseMsg);


            switch (mouseMsg)

            {

                case MouseMessage.WM_LBUTTONDOWN:

                case MouseMessage.WM_RBUTTONDOWN:

                case MouseMessage.WM_MBUTTONDOWN:


                    return this.OnRaiseMouseDown(button, 1, mouseHookStruct.pt.X, mouseHookStruct.pt.Y, mouseHookStruct.mouseData);


                case MouseMessage.WM_LBUTTONUP:

                case MouseMessage.WM_MBUTTONUP:

                case MouseMessage.WM_RBUTTONUP:


                    return this.OnRaiseMouseUp(button, 1, mouseHookStruct.pt.X, mouseHookStruct.pt.Y, mouseHookStruct.mouseData);


                case MouseMessage.WM_LBUTTONDBLCLK:

                case MouseMessage.WM_RBUTTONDBLCLK:

                case MouseMessage.WM_MBUTTONDBLCLK:


                    return this.OnRaiseMouseDoubleClick(button, 2, mouseHookStruct.pt.X, mouseHookStruct.pt.Y, mouseHookStruct.mouseData);


                case MouseMessage.WM_MOUSEMOVE:


                    return this.OnRaiseMouseMove(MouseButtons.None, 0, mouseHookStruct.pt.X, mouseHookStruct.pt.Y, mouseHookStruct.mouseData);

                default:

                    return HookHelper.CallNextHookEx(m_HookId, nCode, wParam, lParam);

            }

        }


        private MouseButtons GetMouseButtons(MouseMessage mouseMsg)

        {

            MouseButtons result = MouseButtons.None;

            switch (mouseMsg)

            {

                case MouseMessage.WM_LBUTTONDBLCLK:

                case MouseMessage.WM_LBUTTONDOWN:

                case MouseMessage.WM_LBUTTONUP:

                    result = MouseButtons.Left;

                    break;

                case MouseMessage.WM_MBUTTONDBLCLK:

                case MouseMessage.WM_MBUTTONDOWN:

                case MouseMessage.WM_MBUTTONUP:

                    result = MouseButtons.Middle;

                    break;

                case MouseMessage.WM_RBUTTONDBLCLK:

                case MouseMessage.WM_RBUTTONDOWN:

                case MouseMessage.WM_RBUTTONUP:

                    result = MouseButtons.Right;

                    break;

            }

            return result;

        }


        private int OnRaiseMouseDoubleClick(MouseButtons button, int clicks, int x, int y, intdelta)

        {

            if (this.MouseDoubleClick != null)

            {

                return this.MouseDoubleClick(this, new MouseEventArgs(button, clicks, x, y, delta));

            }

            return 0;

        }


        private int OnRaiseMouseDown(MouseButtons button, int clicks, int x, int y, int delta)

        {

            if (this.MouseDown != null)

            {

                return this.MouseDown(this, new MouseEventArgs(button, clicks, x, y, delta));

            }

            return 0;

        }


        private int OnRaiseMouseUp(MouseButtons button, int clicks, int x, int y, int delta)

        {

            if (this.MouseUp != null)

            {

                return this.MouseUp(this, new MouseEventArgs(button, clicks, x, y, delta));

            }

            return 0;

        }


        private int OnRaiseMouseMove(MouseButtons button, int clicks, int x, int y, int delta)

        {

            if (this.MouseMove != null)

            {

                return this.MouseMove(this, new MouseEventArgs(button, clicks, x, y, delta));

            }

            return 0;

        }

}

第三步,依次实现线程鼠标Hook以及全局鼠标Hook.

 public class MouseHook : MouseHookBase{

        public MouseHook(int threadId = 0)

            : base(threadId)

        { 

        }

    }

    public class GlobalMouseHook : MouseHookBase

    {

        public GlobalMouseHook(IntPtr processId)

            : base(processId)

        { 

        }

 }

第四步,有了鼠标Hook,我们如果在Revit内使用并且拦截鼠标双击元素事件呢?我们继续封装一个元素监控类 ,如下:

public class ElementMonitor

    {

        private static ElementMonitor m_Instance;

        private MouseHook m_MouseHook;

        private bool m_IsMonitor;

        private UIApplication m_UIApplication;


        private ElementMonitor(UIApplication uiApp)

        {

            m_Instance = this;

            m_UIApplication = uiApp;


            m_MouseHook = new MouseHook();

            m_MouseHook.Install();


            m_MouseHook.MouseDoubleClick += OnRaiseMouseDoubleClick;

        }


        /// <summary>

        /// 静态实例,可在入口类判断此实例是否为null,防止重复注册.

        /// </summary>

        public static ElementMonitor Instance

        {

            get

            {

                return m_Instance;

            }

        }


        /// <summary>

        /// 当鼠标双击元素时触发此事件.

        /// </summary>

        public event HookHandler<DoubleClickElementEventArgs> DoubleClickElement;


        /// <summary>

        /// 注册元素监控,并指定是否立即监控.

        /// </summary>

        public static void Register(UIApplication uiApp, bool immediatelyMonitor = true)

        {

            if (uiApp == null)

            {

                throw new ArgumentNullException(nameof(uiApp));

            }


            new ElementMonitor(uiApp)

            {

                m_IsMonitor = immediatelyMonitor

            };

        }


        /// <summary>

        /// 注册元素监控,并指定是否立即监控.

        /// </summary>

        public static void Register(UIControlledApplication uiControllApp, bool immediatelyMonitor = true)

        {

            if (uiControllApp == null)

            {

                throw new ArgumentNullException(nameof(uiControllApp));

            }


            var flag = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.InvokeMethod;


            var uiApp = (UIApplication)uiControllApp.GetType().InvokeMember("getUIApplication", flag, Type.DefaultBinder, uiControllApp, null);


            Register(uiApp, immediatelyMonitor);

        }


        /// <summary>

        /// 返回1,则拦截鼠标消息,返回0则传递给真正消息接收者.

        /// </summary>

        private int OnRaiseMouseDoubleClick(object sender, System.Windows.Forms.MouseEventArgs e)

        {

            if (!m_IsMonitor || e.Button != MouseButtons.Left || e.Clicks != 2)

            {

                return 0;

            }


            var uiDoc = m_UIApplication.ActiveUIDocument;


            if (uiDoc == null)

            {

                return 0;

            }


            var elemIds = uiDoc.Selection.GetElementIds();


            if (elemIds.Count == 1)

            {

                var elem = uiDoc.Document.GetElement(elemIds.First());


                if (elem == null)

                {

                    return 0;

                } 

                if (this.DoubleClickElement == null)

                {

                    return 0;

                } 

                return this.DoubleClickElement(this, new DoubleClickElementEventArgs(elem));

            }


            return 0;

        }

    }

第五步,调用测试,如下

[Transaction(TransactionMode.Manual)]

    public class MouseHookTest : IExternalCommand

    {

        Result IExternalCommand.Execute(ExternalCommandData commandData, ref string message, ElementSet elements)

        {

            if (ElementMonitor.Instance == null)

            {

                ElementMonitor.Register(commandData.Application);

            }


            ElementMonitor.Instance.DoubleClickElement += OnRaiseDoubleClickElement;


            return Result.Succeeded;

        }


        private int OnRaiseDoubleClickElement(object sender, DoubleClickElementEventArgs e)

        {

            if (e.Element == null)

            {

                return 0;

            }


            System.Windows.Forms.MessageBox.Show(string.Format("双击击元素Id: {0}", e.Element.Id)); 

            return 1;


        }

    }



转载请注明来源本文地址:https://www.tuituisoft/blog/2909.html

上一篇:

Revit二次开发教程:Revit族参数可见性设置

下一篇:

Revit二次开发教程:注册Revit插件