如何为快捷键实现键盘处理程序cefSharp

How to implement keyboard handler cefSharp for shortcut keys

我正在构建一个 windows 应用程序来使用 cefSharp 浏览网页。 我需要在该应用程序中实现一些快捷键,谁能告诉我如何实现此功能。

例如

ctrl + tab = move to next tab

我能够跟踪用户是否按下了任何一个键,但无法跟踪多个键的按下。

IKeyboardHandler

public class KeyboardHandler : IKeyboardHandler
{

    public bool OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey)
    {
        bool result = false;
        Debug.WriteLine(String.Format("OnKeyEvent: KeyType: {0} 0x{1:X} Modifiers: {2}", type, windowsKeyCode, modifiers));
        // TODO: Handle MessageNeeded cases here somehow.
        return result;
    }

    public bool OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut)
    {
        const int WM_SYSKEYDOWN = 0x104;
        const int WM_KEYDOWN = 0x100;
        const int WM_KEYUP = 0x101;
        const int WM_SYSKEYUP = 0x105;
        const int WM_CHAR = 0x102;
        const int WM_SYSCHAR = 0x106;
        const int VK_TAB = 0x9;

        bool result = false;

        isKeyboardShortcut = false;

        // Don't deal with TABs by default:
        // TODO: Are there any additional ones we need to be careful of?
        // i.e. Escape, Return, etc...?
        if (windowsKeyCode == VK_TAB)
        {
            return result;
        }

        Control control = browserControl as Control;
        int msgType = 0;
        switch (type)
        {
            case KeyType.RawKeyDown:
                if (isSystemKey)
                {
                    msgType = WM_SYSKEYDOWN;
                }
                else
                {
                    msgType = WM_KEYDOWN;
                }
                break;
            case KeyType.KeyUp:
                if (isSystemKey)
                {
                    msgType = WM_SYSKEYUP;
                }
                else
                {
                    msgType = WM_KEYUP;
                }
                break;
            case KeyType.Char:
                if (isSystemKey)
                {
                    msgType = WM_SYSCHAR;
                }
                else
                {
                    msgType = WM_CHAR;
                }
                break;
            default:
                Trace.Assert(false);
                break;
        }
        // We have to adapt from CEF's UI thread message loop to our fronting WinForm control here.
        // So, we have to make some calls that Application.Run usually ends up handling for us:
        PreProcessControlState state = PreProcessControlState.MessageNotNeeded;
        // We can't use BeginInvoke here, because we need the results for the return value
        // and isKeyboardShortcut. In theory this shouldn't deadlock, because
        // atm this is the only synchronous operation between the two threads.
        control.Invoke(new Action(() =>
        {
            Message msg = new Message() { HWnd = control.Handle, Msg = msgType, WParam = new IntPtr(windowsKeyCode), LParam = new IntPtr(nativeKeyCode) };

            // First comes Application.AddMessageFilter related processing:
            // 99.9% of the time in WinForms this doesn't do anything interesting.
            bool processed = Application.FilterMessage(ref msg);
            if (processed)
            {
                state = PreProcessControlState.MessageProcessed;
            }
            else
            {
                // Next we see if our control (or one of its parents)
                // wants first crack at the message via several possible Control methods.
                // This includes things like Mnemonics/Accelerators/Menu Shortcuts/etc...
                state = control.PreProcessControlMessage(ref msg);
            }
        }));
        if (state == PreProcessControlState.MessageNeeded)
        {
            // TODO: Determine how to track MessageNeeded for OnKeyEvent.
            isKeyboardShortcut = true;
        }
        else if (state == PreProcessControlState.MessageProcessed)
        {
            // Most of the interesting cases get processed by PreProcessControlMessage.
            result = true;
        }
        Debug.WriteLine(String.Format("OnPreKeyEvent: KeyType: {0} 0x{1:X} Modifiers: {2}", type, windowsKeyCode, modifiers));
        Debug.WriteLine(String.Format("OnPreKeyEvent PreProcessControlState: {0}", state));
        return result;
    }

终于有了一个替代方案来实现快捷方式功能而不需要实现 IKeyboardHandler

这里我用Global Keyboard Hook来实现这个。

全局键盘钩子

public class GlobalKeyboardHook
{
    #region Variables and dll import
    #region For key capturing
    [DllImport("user32.dll")]
    static extern int CallNextHookEx(IntPtr hhk, int code, int wParam, ref keyBoardHookStruct lParam);
    [DllImport("user32.dll")]
    static extern IntPtr SetWindowsHookEx(int idHook, LLKeyboardHook callback, IntPtr hInstance, uint theardID);
    [DllImport("user32.dll")]
    static extern bool UnhookWindowsHookEx(IntPtr hInstance);
    [DllImport("kernel32.dll")]
    static extern IntPtr LoadLibrary(string lpFileName);

    public delegate int LLKeyboardHook(int Code, int wParam, ref keyBoardHookStruct lParam);

    public struct keyBoardHookStruct
    {
        public int vkCode;
        public int scanCode;
        public int flags;
        public int time;
        public int dwExtraInfo;
    }

    const int WH_KEYBOARD_LL = 13;
    const int WM_KEYDOWN = 0x0100;
    const int WM_KEYUP = 0x0101;
    const int WM_SYSKEYDOWN = 0x0104;
    const int WM_SYSKEYUP = 0x0105;

    LLKeyboardHook llkh;
    public List<Keys> HookedKeys = new List<Keys>();
    IntPtr Hook = IntPtr.Zero;

    public event KeyEventHandler KeyDown;
    public event KeyEventHandler KeyUp;
    #endregion

    #region For modifier capturing
    /// <summary> 
    /// Gets the state of modifier keys for a given keycode. 
    /// </summary> 
    /// <param name="keyCode">The keyCode</param> 
    /// <returns></returns> 
    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
    public static extern short GetKeyState(int keyCode);

    //Modifier key vkCode constants 
    private const int VK_SHIFT = 0x10;
    private const int VK_CONTROL = 0x11;
    private const int VK_MENU = 0x12;
    private const int VK_CAPITAL = 0x14;
    #endregion 
    #endregion

    #region Constructor
    // This is the Constructor. This is the code that runs every time you create a new GlobalKeyboardHook object
    public GlobalKeyboardHook()
    {
        llkh = new LLKeyboardHook(HookProc);
        // This starts the hook. You can leave this as comment and you have to start it manually 
        // Or delete the comment mark and your hook will start automatically when your program starts (because a new GlobalKeyboardHook object is created)
        // That's why there are duplicates, because you start it twice! I'm sorry, I haven't noticed this...
        // hook(); <-- Choose!
    }
    ~GlobalKeyboardHook()
    { unhook(); }
    #endregion 

    #region Functions and implementation
    /// <summary>
    /// Hook (Start listening keybord events)
    /// </summary>
    public void hook()
    {
        IntPtr hInstance = LoadLibrary("User32");
        Hook = SetWindowsHookEx(WH_KEYBOARD_LL, llkh, hInstance, 0);
    }
    /// <summary>
    /// Unhook (Stop listening keybord events)
    /// </summary>
    public void unhook()
    {
        UnhookWindowsHookEx(Hook);
    }
    /// <summary>
    /// Pass key into event
    /// </summary>
    /// <param name="Code">Key code</param>
    /// <param name="wParam">int event type (keydown/keyup)</param>
    /// <param name="lParam">keyBoardHookStruct enum for detecting key</param>
    /// <returns>next hook call</returns>
    public int HookProc(int Code, int wParam, ref keyBoardHookStruct lParam)
    {
        if (Code >= 0)
        {
            Keys key = (Keys)lParam.vkCode;
            if (HookedKeys.Contains(key))
            {
                //Get modifiers 
                key = AddModifiers(key);
                KeyEventArgs kArg = new KeyEventArgs(key);
                if ((wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) && (KeyDown != null))
                {
                    KeyDown(this, kArg);
                }
                else if ((wParam == WM_KEYUP || wParam == WM_SYSKEYUP) && (KeyUp != null))
                {
                    KeyUp(this, kArg);
                }
                if (kArg.Handled)
                    return 1;
            }
        }
        return CallNextHookEx(Hook, Code, wParam, ref lParam);
    }
    /// <summary> 
    /// Checks whether Alt, Shift, Control or CapsLock 
    /// is pressed at the same time as the hooked key. 
    /// Modifies the keyCode to include the pressed keys. 
    /// </summary> 
    private Keys AddModifiers(Keys key)
    {
        //CapsLock 
        if ((GetKeyState(VK_CAPITAL) & 0x0001) != 0) key = key | Keys.CapsLock;
        //Shift 
        if ((GetKeyState(VK_SHIFT) & 0x8000) != 0) key = key | Keys.Shift;
        //Ctrl 
        if ((GetKeyState(VK_CONTROL) & 0x8000) != 0) key = key | Keys.Control;
        //Alt 
        if ((GetKeyState(VK_MENU) & 0x8000) != 0) key = key | Keys.Alt;
        return key;
    }
    #endregion 
}

开始挂钩

To start hook add below code to form load or application start
//Start listening keybord events
gHook = new GlobalKeyboardHook();
gHook.KeyDown += new KeyEventHandler(gHook_KeyDown);
foreach (Keys key in Enum.GetValues(typeof(Keys)))
{
   gHook.HookedKeys.Add(key);
}
gHook.hook();

停钩

Add this code on application exit(form closing event)    
//Stop listening keybord events
gHook.unhook();

用于快捷方式实现的 Handel 按键按下事件

    public void gHook_KeyDown(object sender, KeyEventArgs e)
    {
        if (this.ContainsFocus)
        {
            if (e.KeyData.ToString().ToUpper().IndexOf("ALT".ToUpper()) >= 0
               && e.KeyValue == 66)//B = 66
            {
                //ALT + B 
                new Thread(() =>
                {
                    // execute this on the gui thread. (winforms)
                    this.Invoke(new Action(delegate
                    {
                        tosBrowserBtnBack_Click(this, new EventArgs());
                    }));

                }).Start();
            }
            else if (e.KeyData.ToString().ToUpper().IndexOf("ALT".ToUpper()) >= 0
              && e.KeyValue == 70)//F = 70
            {
                //ALT + F
                new Thread(() =>
                {
                    // execute this on the gui thread. (winforms)
                    this.Invoke(new Action(delegate
                    {
                        tosBrowserBtnFor_Click(this, new EventArgs());
                    }));

                }).Start();
            }
    }

无论您的应用程序是否有焦点,全局挂钩都适用于每次击键,所以在这里我使用 this.ContainsFocus 检查当前表单是否有焦点,如果有则处理快捷事件。如果您需要在孔应用程序上实现快捷方式,那么您需要检查应用程序是否具有焦点。 为此,您需要创建一个新的 class

应用焦点

public class ApplicationFocus
{
    /// <summary>Returns true if the current application has focus, false otherwise</summary>
    public static bool ApplicationIsActivated()
    {
        var activatedHandle = GetForegroundWindow();
        if (activatedHandle == IntPtr.Zero)
        {
            return false;       // No window is currently activated
        }

        var procId = Process.GetCurrentProcess().Id;
        int activeProcId;
        GetWindowThreadProcessId(activatedHandle, out activeProcId);

        return activeProcId == procId;
    }


    [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
    private static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int GetWindowThreadProcessId(IntPtr handle, out int processId);
}

然后在gHook_KeyDown事件中用ApplicationFocus.ApplicationIsActivated()替换if (this.ContainsFocus)

这对我有用。