David
发布于 2014-08-30 / 4 阅读 / 0 评论 / 0 点赞

C# 實現全局熱鍵 - 鍵盤消息鉤子

2014年9月11日 15:38:27 全局熱鍵不建議用這種方式實現,下篇文章再說另外一種全局熱鍵的實現方式。
全局熱鍵應該怎那麼介紹才好?寫文章開頭好難,嗯,算了。 公司內部使用軟件一般都會需要賬號和密碼登錄,方便管理權限等等,可對於開發人員調試著卻是個阻礙,調試一次輸一次密碼,儘管系統支持自動登錄,密碼又是有一定的有效期,改密碼又要去改自動登錄的密碼(其實這個自動登錄就是替你輸入密碼,關鍵是更新這個自動登錄密碼很麻煩)。 索性自己做個自動輸密碼和賬號的,幸好之前做過QQ客戶端自動登錄,對SendMessage和PostMessage也有了個基礎認識,我可不敢說掌握50%。關於這兩個API怎麼用,自行谷歌,不再重複說了。 上代碼(我是有職業操守的人,關於公司的代碼.....你懂的),都有註釋,其他自己去試吧。 首先是Hook類,封裝成類極大地方便隨時使用。
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Hook
{
    //事件委託
    public delegate void KeyAction(int nCode,IntPtr wParam,Keys Keys);
    class Hook
    {
        //挂钩
        [DllImport("user32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto)]
        private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
        //取消挂钩
        [DllImport("user32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto)]
        private static extern bool UnhookWindowsHookEx(int idHook);
        //调用下一个钩子
        [DllImport("user32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto)]
        private static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr LParam);
        //键盘消息结构
        [StructLayout(LayoutKind.Sequential)]
        private class KeyBoardHookStruct
        {
            public int vkCode;
            public int scanCode;
            public int flags;
            public int time;
            public int dwExtraInfo;
        }
        //私有委托
        private delegate int HookProc(int nCode, IntPtr wParam, IntPtr LParam);
        private static HookProc KeyBoardHookProcedure;
        
        //事件聲明
        //按下
        public static event KeyAction KeyDown;
        //鬆開
        public static event KeyAction KeyUp;
        
        //要用到的变量,标记是否成功设置钩子
        private static int hHook = 0;

        //LowLevel键盘截获,如果是WH_KEYBOARD=2,并不能对系统键盘截取,Acrobat Reader会在你截取之前获得键盘。 
        private  const int WH_KEYBOARD_LL = 13;
        

        private static int KeyBoardHookProc(int nCode, IntPtr wParam, IntPtr lParam)
        {
            KeyBoardHookStruct kbh = (KeyBoardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyBoardHookStruct));
            //使用前需要為KeyUp和KeyDown綁定对应的处理过程,就像使用控件的事件一樣。
           
            //鬆開按鍵
            if (wParam.ToInt32() == 257)
            {
                KeyUp(nCode, wParam, (Keys)kbh.vkCode);
            }
            else
            {
                //按下
               
                if (KeyDown != null)
                {
                    KeyDown(nCode, wParam, (Keys)kbh.vkCode);
                }
            }
            
            //將按鍵消息交給下一個鉤子
            return CallNextHookEx(hHook, nCode, wParam, lParam);
            //這句好像沒什麼用?即使這裡不調用,其他程序還是能收到鍵盤消息,有待研究。
            
        }

        //取消钩子事件
        public static bool Hook_Clear()
        {
            bool retKeyboard = true;
            if (hHook != 0)
            {
                retKeyboard = UnhookWindowsHookEx(hHook);
                hHook = 0;
            }
            return retKeyboard;
        }
        //开始钩子
        public static bool Hook_Start()
        {
            bool retKeyboard = true;
            if (hHook == 0)
            {
                KeyBoardHookProcedure = new HookProc(KeyBoardHookProc);
                hHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyBoardHookProcedure,Process.GetCurrentProcess().MainModule.BaseAddress, 0);
                //如果设置钩子失败. 
                if (hHook == 0)
                {
                    retKeyboard = false;
                    Hook_Clear();
                }
            }
            return retKeyboard;
        }
    }
}
在你複製這段代碼進VS前,請大概地瀏覽下代碼,看清楚再複製進去,不要想當然地Ctrl+A,Ctral+C,Ctral+V。 這裡弄成靜態類,有兩個監聽事件,使用時把監聽事件掛接上,調用下Start,搞定。 還是上點調用代碼,免得自己忘了。 窗體內控件如圖,文本框是richTextbox 001 窗體代碼
        private void Form1_Load(object sender, EventArgs e)
        {
            Hook.KeyDown += new KeyAction(Hook_KeyDown);
            Hook.KeyUp += new KeyAction(Hook_KeyUp);
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Hook.Hook_Clear();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Hook.Hook_Start();
        }

        void Hook_KeyUp(int nCode, IntPtr wParam, Keys Keys)
        {
            if (nCode >= 0)
            {
                //Ctrl、Alt、Shift狀態
                richTextBox1.AppendText("1:ModifierKeys Status:" + ((int)Control.ModifierKeys).ToString() + "r");

                richTextBox1.AppendText("1:Keys: " + Keys.ToString()  + ",鬆開...r");

                richTextBox1.ScrollToCaret();
            }
        }

        void Hook_KeyDown(int nCode, IntPtr wParam, Keys Keys)
        {
            if (nCode >= 0)
            {
                //Ctrl、Alt、Shift狀態
                richTextBox1.AppendText("2:ModifierKeys Status:" + ((int)Control.ModifierKeys).ToString() + "r");

                richTextBox1.AppendText("2:Keys: " + Keys.ToString() + ",按下...r");

                richTextBox1.ScrollToCaret();
            }
        }
OK,就是這麼簡單,就是這麼容易,對於有按鍵消息後做些什麼事情,自己開發去。 運行截圖 002