[转]C# 键盘钩子,真正解决大小写/shift后字符获取问题

留着学习用,转自https://blog.csdn.net/wwh1004/article/details/79287647


作者:wwh1004 
来源:CSDN 
原文:https://blog.csdn.net/wwh1004/article/details/79287647 
版权声明:本文为博主原创文章,转载请附上博文链接!

原文复制,如下:

 


本代码支持在无消息循环的线程上调用键盘钩子,比如控制台程序,并且可以屏蔽按键(在Key*事件中返回false)
代码:

using System;
using System.Threading;
using System.Windows.Forms;
using static FastWin32.NativeMethods;

namespace FastWin32.Hook.WindowMessage
{
    /// <summary>
    /// 参数,触发条件与 <see cref="KeyEventArgs"/> 相同,但此事件有返回值。返回 <see langword="true"/> 表示将此次消息继续发送给下一个钩子,返回 <see langword="false"/> 表示屏蔽此次消息,目标窗口将无法收到此次消息
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    /// <returns></returns>
    public delegate bool HookKeyEventHandler(KeyboardHook sender, KeyEventArgs e);

    /// <summary>
    /// 参数,触发条件与 <see cref="KeyPressEventArgs"/> 相同,但此事件有返回值。返回 <see langword="true"/> 表示将此次消息继续发送给下一个钩子,返回 <see langword="false"/> 表示屏蔽此次消息,目标窗口将无法收到此次消息
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    /// <returns></returns>
    public delegate bool HookKeyPressEventHandler(KeyboardHook sender, KeyPressEventArgs e);

    /// <summary>
    /// 键盘消息钩子
    /// </summary>
    public class KeyboardHook
    {
        private bool _isLowLevel;

        private IntPtr _hookHandle;

        private byte[] _keyboardState = new byte[256];

        private Thread _hookThread;

        /// <summary>
        /// 按键按下事件
        /// </summary>
        public event HookKeyEventHandler KeyDown;

        /// <summary>
        /// 按键弹起事件
        /// </summary>
        public event HookKeyEventHandler KeyUp;

        /// <summary>
        /// 按键按压事件
        /// </summary>
        public event HookKeyPressEventHandler KeyPress;

        /// <summary>
        /// 是否安装
        /// </summary>
        public bool IsInstalled { get; private set; }

        /// <summary>
        /// 是否将输入处理机制附加到顶端窗口的线程,默认为否。如果大小写获取失败或无法正常挂钩键盘消息,可以将此属性设置为 <see langword="true"/>。
        /// </summary>
        public bool IsAttachInput { get; set; }

        /// <summary>
        /// 创建普通全局键盘钩子实例,非LowLevel
        /// </summary>
        public KeyboardHook() : this(false)
        {
        }

        /// <summary>
        /// 创建全局键盘钩子实例
        /// </summary>
        /// <param name="isLowLevel">是否使用低级键盘钩子</param>
        public KeyboardHook(bool isLowLevel)
        {
            _isLowLevel = true;
        }

        /// <summary>
        /// 安装钩子
        /// </summary>
        public bool Install()
        {
            if (IsInstalled)
                throw new NotSupportedException("无法重复安装钩子");

            bool finished;

            finished = false;
            _hookThread = null;
            if (Application.MessageLoop)
            {
                //调用此方法的线程如果有消息循环,就不需要开新线程启动消息循环
                _hookHandle = SetWindowsHookEx(_isLowLevel ? WH_KEYBOARD_LL : WH_KEYBOARD, KeyboardHookCallback, GetModuleHandle(null), 0);
                finished = true;
            }
            else
            {
                _hookThread = new Thread(() =>
                {
                    _hookHandle = SetWindowsHookEx(_isLowLevel ? WH_KEYBOARD_LL : WH_KEYBOARD, KeyboardHookCallback, GetModuleHandle(null), 0);
                    finished = true;
                    Application.Run();
                })
                {
                    IsBackground = true
                };
                _hookThread.Start();
            }
            while (!finished)
                Thread.Sleep(0);
            if (_hookHandle == IntPtr.Zero)
            {
                _hookThread?.Abort();
                return false;
            }
            IsInstalled = true;
            return true;
        }

        /// <summary>
        /// 键盘消息回调函数
        /// </summary>
        /// <param name="nCode">挂钩过程用于确定如何处理消息的代码。如果代码小于0,挂钩过程必须将消息传递给CallNextHookEx函数,无需进一步处理,并应返回CallNextHookEx返回的值。</param>
        /// <param name="wParam">产生击键消息的密钥的虚拟密钥代码。</param>
        /// <param name="lParam">重复计数,扫描码,扩展密钥标志,上下文代码,先前的密钥状态标志和转换状态标志。有关lParam参数的更多信息,请参阅按键消息标志。下表描述了该值的位。</param>
        /// <returns></returns>
        private unsafe IntPtr KeyboardHookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode < 0 || (KeyDown == null && KeyUp == null && KeyPress == null))
                //如果nCode小于零,则钩子过程必须返回CallNextHookEx返回的值并且不对钩子消息做处理。如果3个事件均未被订阅,直接返回
                return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
            else
            {
                if (OnEvent((uint)wParam, ((KBDLLHOOKSTRUCT*)lParam)->vkCode, ((KBDLLHOOKSTRUCT*)lParam)->scanCode))
                    return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
                else
                    return (IntPtr)(-1);
            }
        }

        /// <summary>
        /// 引发事件
        /// </summary>
        /// <param name="messageType">消息类型</param>
        /// <param name="vkCode">虚拟键码</param>
        /// <param name="scanCode">扫描码</param>
        /// <returns></returns>
        private unsafe bool OnEvent(uint messageType, uint vkCode, uint scanCode)
        {
            bool isCallNext;
            uint currentThreadId;
            uint foregroundThreadId;
            char keyChar;

            isCallNext = true;
            if (KeyDown != null && (messageType == WM_KEYDOWN || messageType == WM_SYSKEYDOWN))
                isCallNext = KeyDown(this, new KeyEventArgs((Keys)vkCode));
            if (KeyUp != null && (messageType == WM_KEYUP || messageType == WM_SYSKEYUP))
                isCallNext = KeyUp(this, new KeyEventArgs((Keys)vkCode));
            if (KeyPress != null && messageType == WM_KEYDOWN)
            {
                if (IsAttachInput && ((currentThreadId = GetCurrentThreadId()) != (foregroundThreadId = GetWindowThreadProcessId(GetForegroundWindow(), null))))
                {
                    //将输入处理机制附加到顶端窗口的线程
                    AttachThreadInput(currentThreadId, foregroundThreadId, true);
                    GetKeyState(0);
                    GetKeyboardState(_keyboardState);
                    AttachThreadInput(currentThreadId, foregroundThreadId, false);
                }
                else
                {
                    GetKeyState(0);
                    GetKeyboardState(_keyboardState);
                }
                if (ToAscii(vkCode, scanCode, _keyboardState, out keyChar, 0) == 1)
                    isCallNext = KeyPress(this, new KeyPressEventArgs(keyChar));
            }
            return isCallNext;
        }

        /// <summary>
        /// 卸载钩子
        /// </summary>
        public bool Uninstall()
        {
            if (!IsInstalled)
                throw new NotSupportedException("未安装钩子");

            _hookThread?.Abort();
            if (!UnhookWindowsHookEx(_hookHandle))
                //钩子卸载失败
                return false;
            IsInstalled = false;
            return true;
        }
    }
}

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值