Detecting text changes in Word 2016 from VSTO add-in

前端 未结 2 1707
[愿得一人]
[愿得一人] 2020-12-01 08:09

This question is very closely related to How to get the “KeyPress” event from a Word 2010 Addin (developed in C#)? (and in fact includes the sample code from the answer to t

2条回答
  •  粉色の甜心
    2020-12-01 08:51

    Everthing should work fine if you don't use a low-level hook in your VSTO add-in.

    [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern int GetCurrentThreadId();
    
    const int WH_KEYBOARD = 2;
    
    private static IntPtr SetHook(HookProcedure procedure)
    {
        var threadId = (uint)SafeNativeMethods.GetCurrentThreadId();
        return SetWindowsHookEx(WH_KEYBOARD, procedure, IntPtr.Zero, threadId);
    }
    

    Please note that you probably also need to create a hook to intercept mouse messages as it is possible to modify the text of a document solely by mouse interactions (e.g. copy and paste via Ribbon or context menu).

    VSTO Sample

    Here is a complete working VSTO sample including keyboard and mouse hooks:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;
    using Office = Microsoft.Office.Core;
    
    namespace SampleAddinWithKeyboardHook
    {
        public partial class ThisAddIn
        {
            // NOTE: We need a backing field to prevent the delegate being garbage collected
            private SafeNativeMethods.HookProc _mouseProc;
            private SafeNativeMethods.HookProc _keyboardProc;
    
            private IntPtr _hookIdMouse;
            private IntPtr _hookIdKeyboard;
    
            private void ThisAddIn_Startup(object sender, EventArgs e)
            {
                _mouseProc = MouseHookCallback;
                _keyboardProc = KeyboardHookCallback;
    
                SetWindowsHooks();
            }
    
            private void ThisAddIn_Shutdown(object sender, EventArgs e)
            {
                UnhookWindowsHooks();
            }
    
            private void SetWindowsHooks()
            {
                uint threadId = (uint)SafeNativeMethods.GetCurrentThreadId();
    
                _hookIdMouse =
                    SafeNativeMethods.SetWindowsHookEx(
                        (int)SafeNativeMethods.HookType.WH_MOUSE,
                        _mouseProc,
                        IntPtr.Zero,
                        threadId);
    
                _hookIdKeyboard =
                    SafeNativeMethods.SetWindowsHookEx(
                        (int)SafeNativeMethods.HookType.WH_KEYBOARD,
                        _keyboardProc,
                        IntPtr.Zero,
                        threadId);
            }
    
            private void UnhookWindowsHooks()
            {
                SafeNativeMethods.UnhookWindowsHookEx(_hookIdKeyboard);
                SafeNativeMethods.UnhookWindowsHookEx(_hookIdMouse);
            }
    
            private IntPtr MouseHookCallback(int nCode, IntPtr wParam, IntPtr lParam)
            {
                if (nCode >= 0)
                {
                    var mouseHookStruct =
                        (SafeNativeMethods.MouseHookStructEx)
                            Marshal.PtrToStructure(lParam, typeof(SafeNativeMethods.MouseHookStructEx));
    
                    // handle mouse message here
                    var message = (SafeNativeMethods.WindowMessages)wParam;
                    Debug.WriteLine(
                        "{0} event detected at position {1} - {2}",
                        message,
                        mouseHookStruct.pt.X,
                        mouseHookStruct.pt.Y);
                }
                return SafeNativeMethods.CallNextHookEx(
                    _hookIdKeyboard,
                    nCode,
                    wParam,
                    lParam);
            }
    
            private IntPtr KeyboardHookCallback(int nCode, IntPtr wParam, IntPtr lParam)
            {
                if (nCode >= 0)
                {
                    // handle key message here
                    Debug.WriteLine("Key event detected.");
                }
    
                return SafeNativeMethods.CallNextHookEx(
                    _hookIdKeyboard,
                    nCode,
                    wParam,
                    lParam);
            }
    
            #region VSTO generated code
    
            /// 
            /// Required method for Designer support.
            /// 
            private void InternalStartup()
            {
                Startup += ThisAddIn_Startup;
                Shutdown += ThisAddIn_Shutdown;
            }
    
            #endregion
        }
    
        internal static class SafeNativeMethods
        {
            public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
    
            public enum HookType
            {
                WH_KEYBOARD = 2,
                WH_MOUSE = 7
            }
    
            public enum WindowMessages : uint
            {
                WM_KEYDOWN = 0x0100,
                WM_KEYFIRST = 0x0100,
                WM_KEYLAST = 0x0108,
                WM_KEYUP = 0x0101,
                WM_LBUTTONDBLCLK = 0x0203,
                WM_LBUTTONDOWN = 0x0201,
                WM_LBUTTONUP = 0x0202,
                WM_MBUTTONDBLCLK = 0x0209,
                WM_MBUTTONDOWN = 0x0207,
                WM_MBUTTONUP = 0x0208,
                WM_MOUSEACTIVATE = 0x0021,
                WM_MOUSEFIRST = 0x0200,
                WM_MOUSEHOVER = 0x02A1,
                WM_MOUSELAST = 0x020D,
                WM_MOUSELEAVE = 0x02A3,
                WM_MOUSEMOVE = 0x0200,
                WM_MOUSEWHEEL = 0x020A,
                WM_MOUSEHWHEEL = 0x020E,
                WM_RBUTTONDBLCLK = 0x0206,
                WM_RBUTTONDOWN = 0x0204,
                WM_RBUTTONUP = 0x0205,
                WM_SYSDEADCHAR = 0x0107,
                WM_SYSKEYDOWN = 0x0104,
                WM_SYSKEYUP = 0x0105
            }
    
            [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            public static extern IntPtr GetModuleHandle(string lpModuleName);
    
            [DllImport("user32.dll", SetLastError = true)]
            public static extern bool UnhookWindowsHookEx(IntPtr hhk);
    
            [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            public static extern IntPtr SetWindowsHookEx(
                int idHook,
                HookProc lpfn,
                IntPtr hMod,
                uint dwThreadId);
    
            [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            public static extern IntPtr CallNextHookEx(
                IntPtr hhk,
                int nCode,
                IntPtr wParam,
                IntPtr lParam);
    
            [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
            public static extern int GetCurrentThreadId();
    
            [StructLayout(LayoutKind.Sequential)]
            public struct Point
            {
                public int X;
                public int Y;
    
                public Point(int x, int y)
                {
                    X = x;
                    Y = y;
                }
    
                public static implicit operator System.Drawing.Point(Point p)
                {
                    return new System.Drawing.Point(p.X, p.Y);
                }
    
                public static implicit operator Point(System.Drawing.Point p)
                {
                    return new Point(p.X, p.Y);
                }
            }
    
            [StructLayout(LayoutKind.Sequential)]
            public struct MouseHookStructEx
            {
                public Point pt;
                public IntPtr hwnd;
                public uint wHitTestCode;
                public IntPtr dwExtraInfo;
                public int MouseData;
            }
        }
    }
    

    VBE Add-in Sample

    And here is a working sample for the VBA editor (VBE add-in):

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;
    using Extensibility;
    
    namespace VbeAddin
    {
        [ComVisible(true)]
        [ProgId("VbeAddin.Connect")]
        [Guid("95840C70-5A1A-4EDB-B436-40E8BF030469")]
        public class Connect : StandardOleMarshalObject, IDTExtensibility2
        {
            // NOTE: We need a backing field to prevent the delegate being garbage collected
            private SafeNativeMethods.HookProc _mouseProc;
            private SafeNativeMethods.HookProc _keyboardProc;
    
            private IntPtr _hookIdMouse;
            private IntPtr _hookIdKeyboard;
    
            #region IDTExtensibility2 Members
    
            public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
            {
                _mouseProc = MouseHookCallback;
                _keyboardProc = KeyboardHookCallback;
    
                SetWindowsHooks();
            }
    
            public void OnDisconnection(ext_DisconnectMode removeMode, ref Array custom)
            {
                UnhookWindowsHooks();
            }
    
            public void OnAddInsUpdate(ref Array custom)
            {
            }
    
            public void OnStartupComplete(ref Array custom)
            {
            }
    
            public void OnBeginShutdown(ref Array custom)
            {
            }
    
            #endregion
    
            private void SetWindowsHooks()
            {
                uint threadId = (uint)SafeNativeMethods.GetCurrentThreadId();
    
                _hookIdMouse =
                    SafeNativeMethods.SetWindowsHookEx(
                        (int)SafeNativeMethods.HookType.WH_MOUSE,
                        _mouseProc,
                        IntPtr.Zero,
                        threadId);
    
                _hookIdKeyboard =
                    SafeNativeMethods.SetWindowsHookEx(
                        (int)SafeNativeMethods.HookType.WH_KEYBOARD,
                        _keyboardProc,
                        IntPtr.Zero,
                        threadId);
            }
    
            private void UnhookWindowsHooks()
            {
                SafeNativeMethods.UnhookWindowsHookEx(_hookIdKeyboard);
                SafeNativeMethods.UnhookWindowsHookEx(_hookIdMouse);
            }
    
            private IntPtr MouseHookCallback(int nCode, IntPtr wParam, IntPtr lParam)
            {
                if (nCode >= 0)
                {
                    var mouseHookStruct =
                        (SafeNativeMethods.MouseHookStructEx)
                            Marshal.PtrToStructure(
                                lParam,
                                typeof(SafeNativeMethods.MouseHookStructEx));
    
                    // handle mouse message here
                    var message = (SafeNativeMethods.WindowMessages)wParam;
                    Debug.WriteLine(
                        "{0} event detected at position {1} - {2}",
                        message,
                        mouseHookStruct.pt.X,
                        mouseHookStruct.pt.Y);
                }
                return SafeNativeMethods.CallNextHookEx(
                    _hookIdKeyboard,
                    nCode,
                    wParam,
                    lParam);
            }
    
            private IntPtr KeyboardHookCallback(int nCode, IntPtr wParam, IntPtr lParam)
            {
                if (nCode >= 0)
                {
                    // handle key message here
                    Debug.WriteLine("Key event detected.");
                }
                return SafeNativeMethods.CallNextHookEx(
                    _hookIdKeyboard,
                    nCode,
                    wParam,
                    lParam);
            }
        }
    
        internal static class SafeNativeMethods
        {
            public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
    
            public enum HookType
            {
                WH_KEYBOARD = 2,
                WH_MOUSE = 7
            }
    
            public enum WindowMessages : uint
            {
                WM_KEYDOWN = 0x0100,
                WM_KEYFIRST = 0x0100,
                WM_KEYLAST = 0x0108,
                WM_KEYUP = 0x0101,
                WM_LBUTTONDBLCLK = 0x0203,
                WM_LBUTTONDOWN = 0x0201,
                WM_LBUTTONUP = 0x0202,
                WM_MBUTTONDBLCLK = 0x0209,
                WM_MBUTTONDOWN = 0x0207,
                WM_MBUTTONUP = 0x0208,
                WM_MOUSEACTIVATE = 0x0021,
                WM_MOUSEFIRST = 0x0200,
                WM_MOUSEHOVER = 0x02A1,
                WM_MOUSELAST = 0x020D,
                WM_MOUSELEAVE = 0x02A3,
                WM_MOUSEMOVE = 0x0200,
                WM_MOUSEWHEEL = 0x020A,
                WM_MOUSEHWHEEL = 0x020E,
                WM_RBUTTONDBLCLK = 0x0206,
                WM_RBUTTONDOWN = 0x0204,
                WM_RBUTTONUP = 0x0205,
                WM_SYSDEADCHAR = 0x0107,
                WM_SYSKEYDOWN = 0x0104,
                WM_SYSKEYUP = 0x0105
            }
    
            [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            public static extern IntPtr GetModuleHandle(string lpModuleName);
    
            [DllImport("user32.dll", SetLastError = true)]
            public static extern bool UnhookWindowsHookEx(IntPtr hhk);
    
            [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            public static extern IntPtr SetWindowsHookEx(
                int idHook,
                HookProc lpfn,
                IntPtr hMod,
                uint dwThreadId);
    
            [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            public static extern IntPtr CallNextHookEx(
                IntPtr hhk,
                int nCode,
                IntPtr wParam,
                IntPtr lParam);
    
            [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
            public static extern int GetCurrentThreadId();
    
            [StructLayout(LayoutKind.Sequential)]
            public struct Point
            {
                public int X;
                public int Y;
            }
    
            [StructLayout(LayoutKind.Sequential)]
            public struct MouseHookStructEx
            {
                public Point pt;
                public IntPtr hwnd;
                public uint wHitTestCode;
                public IntPtr dwExtraInfo;
                public int MouseData;
            }
        }
    }
    

提交回复
热议问题