• C# 实现屏幕键盘 (ScreenKeyboard)


    原文地址:http://www.cnblogs.com/youzai/archive/2008/05/19/1202732.html

       要实现一个屏幕键盘,需要监听所有键盘事件,无论窗体是否被激活。因此需要一个全局的钩子,也就
    是系统范围的钩子。

    什么是钩子(Hook)

        钩子(Hook)是Windows提供的一种消息处理机制平台,是指在程序正常运行中接受信息之前预先
        启动的函数,用来检查和修改传给该程序的信息,(钩子)实际上是一个处理消息的程序段,通
        过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获
        该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不
        作处理而继续传递该消息,还可以强制结束消息的传递。注意:安装钩子函数将会影响系统的性
        能。监测“系统范围事件”的系统钩子特别明显。因为系统在处理所有的相关事件时都将调用您的
        钩子函数,这样您的系统将会明显的减慢。所以应谨慎使用,用完后立即卸载。还有,由于您可
        以预先截获其它进程的消息,所以一旦您的钩子函数出了问题的话必将影响其它的进程。

    钩子的作用范围
        一共有两种范围(类型)的钩子,局部的和远程的。局部钩子仅钩挂自己进程的事件。远程的钩
        子还可以将钩挂其它进程发生的事件。远程的钩子又有两种: 基于线程的钩子将捕获其它进程中
        某一特定线程的事件。简言之,就是可以用来观察其它进程中的某一特定线程将发生的事件。 系
        统范围的钩子将捕捉系统中所有进程将发生的事件消息。 

    Hook 类型 
        Windows共有14种Hooks,每一种类型的Hook可以使应用程序能够监视不同类型的系统消息处理机
        制。下面描述所有可以利用的Hook类型的发生时机。详细内容可以查阅MSDN,这里只介绍我们将要
        用到的两种类型的钩子。
         
        (1)WH_KEYBOARD_LL Hook 
            WH_KEYBOARD_LL Hook监视输入到线程消息队列中的键盘消息。

        (2)WH_MOUSE_LL Hook 
            WH_MOUSE_LL Hook监视输入到线程消息队列中的鼠标消息。

    下面的 class 把 API 调用封装起来以便调用。

     1// NativeMethods.cs
     2using System;
     3using System.Runtime.InteropServices;
     4using System.Drawing;
     5
     6namespace CnBlogs.Youzai.ScreenKeyboard {
     7    [StructLayout(LayoutKind.Sequential)]
     8    internal struct MOUSEINPUT {
     9        public int dx;
    10        public int dy;
    11        public int mouseData;
    12        public int dwFlags;
    13        public int time;
    14        public IntPtr dwExtraInfo;
    15    }
    16
    17    [StructLayout(LayoutKind.Sequential)]
    18    internal struct KEYBDINPUT {
    19        public short wVk;
    20        public short wScan;
    21        public int dwFlags;
    22        public int time;
    23        public IntPtr dwExtraInfo;
    24    }
    25
    26    [StructLayout(LayoutKind.Explicit)]
    27    internal struct Input {
    28        [FieldOffset(0)]
    29        public int type;
    30        [FieldOffset(4)]
    31        public MOUSEINPUT mi;
    32        [FieldOffset(4)]
    33        public KEYBDINPUT ki;
    34        [FieldOffset(4)]
    35        public HARDWAREINPUT hi;
    36    }
    37
    38    [StructLayout(LayoutKind.Sequential)]
    39    internal struct HARDWAREINPUT {
    40        public int uMsg;
    41        public short wParamL;
    42        public short wParamH;
    43    }
    44
    45    internal class INPUT {
    46        public const int MOUSE = 0;
    47        public const int KEYBOARD = 1;
    48        public const int HARDWARE = 2;
    49    }
    50
    51    internal static class NativeMethods {
    52        [DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = false)]
    53        internal static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);
    54
    55        [DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = false)]
    56        internal static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
    57
    58        [DllImport("User32.dll", EntryPoint = "SendInput", CharSet = CharSet.Auto)]
    59        internal static extern UInt32 SendInput(UInt32 nInputs, Input[] pInputs, Int32 cbSize);
    60
    61        [DllImport("Kernel32.dll", EntryPoint = "GetTickCount", CharSet = CharSet.Auto)]
    62        internal static extern int GetTickCount();
    63
    64        [DllImport("User32.dll", EntryPoint = "GetKeyState", CharSet = CharSet.Auto)]
    65        internal static extern short GetKeyState(int nVirtKey);
    66
    67        [DllImport("User32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
    68        internal static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
    69    }
    70}

    安装钩子
        使用SetWindowsHookEx函数(API函数),指定一个Hook类型、自己的Hook过程是全局还是局部Hook,
        同时给出Hook过程的进入点,就可以轻松的安装自己的Hook过程。SetWindowsHookEx总是将你的Hook函
        数放置在Hook链的顶端。你可以使用CallNextHookEx函数将系统消息传递给Hook链中的下一个函数。
        对于某些类型的Hook,系统将向该类的所有Hook函数发送消息,这时, 
        Hook函数中的CallNextHookEx语句将被忽略。全局(远程钩子)Hook函数可以拦截系统中所有线程的某
        个特定的消息,为了安装一个全局Hook过程,必须在应用程序外建立一个DLL并将该Hook函数封装到其中,
        应用程序在安装全局Hook过程时必须先得到该DLL模块的句柄。将Dll名传递给LoadLibrary 函数,就会得
        到该DLL模块的句柄;得到该句柄 后,使用GetProcAddress函数可以得到Hook过程的地址。最后,使用
        SetWindowsHookEx将 Hook过程的首址嵌入相应的Hook链中,SetWindowsHookEx传递一个模块句柄,它为
        Hook过程的进入点,线程标识符置为0,该Hook过程同系统中的所有线程关联。如果是安装局部Hook此时
        该Hook函数可以放置在DLL中,也可以放置在应用程序的模块段。在C#中通过平台调用(前文已经介绍过)
        来调用API函数。

     1    public void Start(bool installMouseHook, bool installKeyboardHook) {
     2        if (hMouseHook == IntPtr.Zero && installMouseHook) {
     3            MouseHookProcedure = new HookProc(MouseHookProc);
     4            hMouseHook = SetWindowsHookEx(
     5                WH_MOUSE_LL,
     6                MouseHookProcedure,
     7                Marshal.GetHINSTANCE(
     8                Assembly.GetExecutingAssembly().GetModules()[0]),
     9                0
    10           );
    11
    12            if (hMouseHook == IntPtr.Zero) {
    13                int errorCode = Marshal.GetLastWin32Error();
    14                Stop(true, false, false);
    15
    16                throw new Win32Exception(errorCode);
    17            }
    18        }
    19
    20        if (hKeyboardHook == IntPtr.Zero && installKeyboardHook) {
    21            KeyboardHookProcedure = new HookProc(KeyboardHookProc);
    22            //install hook
    23            hKeyboardHook = SetWindowsHookEx(
    24                WH_KEYBOARD_LL,
    25                KeyboardHookProcedure,
    26                Marshal.GetHINSTANCE(
    27                Assembly.GetExecutingAssembly().GetModules()[0]),
    28                0);
    29            // If SetWindowsHookEx fails.
    30            if (hKeyboardHook == IntPtr.Zero) {
    31                // Returns the error code returned by the last 
    32                // unmanaged function called using platform invoke 
    33                // that has the DllImportAttribute.SetLastError flag set. 
    34                int errorCode = Marshal.GetLastWin32Error();
    35                //do cleanup
    36                Stop(false, true, false);
    37                //Initializes and throws a new instance of the 
    38                // Win32Exception class with the specified error. 
    39                throw new Win32Exception(errorCode);
    40            }
    41        }
    42    }

    使用完钩子后,要进行卸载,这个可以写在析构函数中。

     1
     2    public void Stop() {
     3        this.Stop(true, true, true);
     4    }
     5    
     6    public void Stop(bool uninstallMouseHook, bool uninstallKeyboardHook, 
     7        bool throwExceptions) {
     8        // if mouse hook set and must be uninstalled
     9        if (hMouseHook != IntPtr.Zero && uninstallMouseHook) {
    10            // uninstall hook
    11            bool retMouse = UnhookWindowsHookEx(hMouseHook);
    12            // reset invalid handle
    13            hMouseHook = IntPtr.Zero;
    14            // if failed and exception must be thrown
    15            if (retMouse == false && throwExceptions) {
    16                // Returns the error code returned by the last unmanaged function 
    17                // called using platform invoke that has the DllImportAttribute.
    18                // SetLastError flag set. 
    19                int errorCode = Marshal.GetLastWin32Error();
    20                // Initializes and throws a new instance of the Win32Exception class 
    21                // with the specified error. 
    22                throw new Win32Exception(errorCode);
    23            }
    24        }
    25
    26        // if keyboard hook set and must be uninstalled
    27        if (hKeyboardHook != IntPtr.Zero && uninstallKeyboardHook) {
    28            // uninstall hook
    29            bool retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
    30            // reset invalid handle
    31            hKeyboardHook = IntPtr.Zero;
    32            // if failed and exception must be thrown
    33            if (retKeyboard == false && throwExceptions) {
    34                // Returns the error code returned by the last unmanaged function 
    35                // called using platform invoke that has the DllImportAttribute.
    36                // SetLastError flag set. 
    37                int errorCode = Marshal.GetLastWin32Error();
    38                // Initializes and throws a new instance of the Win32Exception class 
    39                // with the specified error. 
    40                throw new Win32Exception(errorCode);
    41            }
    42        }
    43    }
    44

    将这个文件编译成一个dll,即可在应用程序中调用。通过它提供的事件,便可监听所有的键盘事件。
    但是,这只能监听键盘事件,没有键盘的情况下,怎么会有键盘事件?其实很简单,通过SendInput 
    API函数提供虚拟键盘代码的调用即可模拟键盘输入。下面的代码模拟一个 KeyDown 和 KeyUp 过程,
    把他们连接起来就是一次按键过程。

     1    private void SendKeyDown(short key) {
     2        Input[] input = new Input[1];
     3        input[0].type = INPUT.KEYBOARD;
     4        input[0].ki.wVk = key;
     5        input[0].ki.time = NativeMethods.GetTickCount();
     6
     7        if (NativeMethods.SendInput((uint)input.Length, input, Marshal.SizeOf(input[0])) 
     8            < input.Length) {
     9            throw new Win32Exception(Marshal.GetLastWin32Error());
    10        }
    11    }
    12
    13    private void SendKeyUp(short key) {
    14        Input[] input = new Input[1];
    15        input[0].type = INPUT.KEYBOARD;
    16        input[0].ki.wVk = key;
    17        input[0].ki.dwFlags = KeyboardConstaint.KEYEVENTF_KEYUP;
    18        input[0].ki.time = NativeMethods.GetTickCount();
    19
    20        if (NativeMethods.SendInput((uint)input.Length, input, Marshal.SizeOf(input[0]))
    21            < input.Length) {
    22            throw new Win32Exception(Marshal.GetLastWin32Error());
    23        }
    24    }

    自己实现一个 KeyBoardButton 控件用作按钮,用 Visual Studio 或者 SharpDevelop 为屏幕键盘设计 UI,然后
    在这些 Button 的 Click 事件里面模拟一个按键过程。

     1
     2    private void ButtonOnClick(object sender, EventArgs e) {
     3        KeyboardButton btnKey = sender as KeyboardButton;
     4        if (btnKey == null) {
     5            return;
     6        }
     7
     8        SendKeyCommand(btnKey);
     9    }
    10    
    11    private void SendKeyCommand(KeyboardButton keyButton) {
    12        short key = keyButton.VKCode;
    13        if (combinationVKButtonsMap.ContainsKey(key)) {
    14            if (keyButton.Checked) {
    15                SendKeyUp(key);
    16            } else {
    17                SendKeyDown(key);
    18            }
    19        } else {
    20            SendKeyDown(key);
    21            SendKeyUp(key);
    22        }
    23    }

    其中 combinationVKButtonsMap 是一个 IDictionary<short, IList<KeyboardButton>>, key 存储的是
    VK_SHIFT, VK_CONTROL 等组合键的键盘码。左右两个按钮对应同一个键盘码,因此需要放在一个 List 里。
    标准键盘上的每一个键都有虚拟键码( VK_CODE)与之对应。还有一些其他的常量,
    把它写在一个静态 class 里吧。

     1    // KeyboardConstaint.cs
     2    internal static class KeyboardConstaint {
     3        internal static readonly short VK_F1 = 0x70;
     4        internal static readonly short VK_F2 = 0x71;
     5        internal static readonly short VK_F3 = 0x72;
     6        internal static readonly short VK_F4 = 0x73;
     7        internal static readonly short VK_F5 = 0x74;
     8        internal static readonly short VK_F6 = 0x75;
     9        internal static readonly short VK_F7 = 0x76;
    10        internal static readonly short VK_F8 = 0x77;
    11        internal static readonly short VK_F9 = 0x78;
    12        internal static readonly short VK_F10 = 0x79;
    13        internal static readonly short VK_F11 = 0x7A;
    14        internal static readonly short VK_F12 = 0x7B;
    15
    16        internal static readonly short VK_LEFT = 0x25;
    17        internal static readonly short VK_UP = 0x26;
    18        internal static readonly short VK_RIGHT = 0x27;
    19        internal static readonly short VK_DOWN = 0x28;
    20
    21        internal static readonly short VK_NONE = 0x00;
    22        internal static readonly short VK_ESCAPE = 0x1B;
    23        internal static readonly short VK_EXECUTE = 0x2B;
    24        internal static readonly short VK_CANCEL = 0x03;
    25        internal static readonly short VK_RETURN = 0x0D;
    26        internal static readonly short VK_ACCEPT = 0x1E;
    27        internal static readonly short VK_BACK = 0x08;
    28        internal static readonly short VK_TAB = 0x09;
    29        internal static readonly short VK_DELETE = 0x2E;
    30        internal static readonly short VK_CAPITAL = 0x14;
    31        internal static readonly short VK_NUMLOCK = 0x90;
    32        internal static readonly short VK_SPACE = 0x20;
    33        internal static readonly short VK_DECIMAL = 0x6E;
    34        internal static readonly short VK_SUBTRACT = 0x6D;
    35
    36        internal static readonly short VK_ADD = 0x6B;
    37        internal static readonly short VK_DIVIDE = 0x6F;
    38        internal static readonly short VK_MULTIPLY = 0x6A;
    39        internal static readonly short VK_INSERT = 0x2D;
    40
    41        internal static readonly short VK_OEM_1 = 0xBA;  // ';:' for US
    42        internal static readonly short VK_OEM_PLUS = 0xBB;  // '+' any country
    43
    44        internal static readonly short VK_OEM_MINUS = 0xBD;  // '-' any country
    45
    46        internal static readonly short VK_OEM_2 = 0xBF;  // '/?' for US
    47        internal static readonly short VK_OEM_3 = 0xC0;  // '`~' for US
    48        internal static readonly short VK_OEM_4 = 0xDB;  //  '[{' for US
    49        internal static readonly short VK_OEM_5 = 0xDC;  //  '|' for US
    50        internal static readonly short VK_OEM_6 = 0xDD;  //  ']}' for US
    51        internal static readonly short VK_OEM_7 = 0xDE;  //  ''"' for US
    52        internal static readonly short VK_OEM_PERIOD = 0xBE;  // '.>' any country
    53        internal static readonly short VK_OEM_COMMA = 0xBC;  // ',<' any country
    54        internal static readonly short VK_SHIFT = 0x10;
    55        internal static readonly short VK_CONTROL = 0x11;
    56        internal static readonly short VK_MENU = 0x12;
    57        internal static readonly short VK_LWIN = 0x5B;
    58        internal static readonly short VK_RWIN = 0x5C;
    59        internal static readonly short VK_APPS = 0x5D;
    60
    61        internal static readonly short VK_LSHIFT = 0xA0;
    62        internal static readonly short VK_RSHIFT = 0xA1;
    63        internal static readonly short VK_LCONTROL = 0xA2;
    64        internal static readonly short VK_RCONTROL = 0xA3;
    65        internal static readonly short VK_LMENU = 0xA4;
    66        internal static readonly short VK_RMENU = 0xA5;
    67
    68        internal static readonly short VK_SNAPSHOT = 0x2C;
    69        internal static readonly short VK_SCROLL = 0x91;
    70        internal static readonly short VK_PAUSE = 0x13;
    71        internal static readonly short VK_HOME = 0x24;
    72
    73        internal static readonly short VK_NEXT = 0x22;
    74        internal static readonly short VK_PRIOR = 0x21;
    75        internal static readonly short VK_END = 0x23;
    76
    77        internal static readonly short VK_NUMPAD0 = 0x60;
    78        internal static readonly short VK_NUMPAD1 = 0x61;
    79        internal static readonly short VK_NUMPAD2 = 0x62;
    80        internal static readonly short VK_NUMPAD3 = 0x63;
    81        internal static readonly short VK_NUMPAD4 = 0x64;
    82        internal static readonly short VK_NUMPAD5 = 0x65;
    83        internal static readonly short VK_NUMPAD5NOTHING = 0x0C;
    84        internal static readonly short VK_NUMPAD6 = 0x66;
    85        internal static readonly short VK_NUMPAD7 = 0x67;
    86        internal static readonly short VK_NUMPAD8 = 0x68;
    87        internal static readonly short VK_NUMPAD9 = 0x69;
    88
    89        internal static readonly short KEYEVENTF_EXTENDEDKEY    = 0x0001;
    90        internal static readonly short KEYEVENTF_KEYUP          = 0x0002;
    91
    92        internal static readonly int GWL_EXSTYLE    = -20;
    93        internal static readonly int WS_DISABLED    = 0X8000000;
    94        internal static readonly int WM_SETFOCUS    = 0X0007;
    95    }

    屏幕键盘必须是一个不能获得输入焦点的窗体,在这个窗体的构造函数里,可以安装
    一个全局鼠标钩子,再通过调用 SetWindowLong API 函数完成。

     1UserActivityHook hook = new UserActivityHook(true, true);
     2hook.MouseActivity += HookOnMouseActivity;
     3
     4private void HookOnMouseActivity(object sener, HookEx.MouseExEventArgs e) {
     5    Point location = e.Location;
     6
     7    if (e.Button == MouseButtons.Left) {
     8        Rectangle captionRect = new Rectangle(this.Location, new Size(this.Width, 
     9            SystemInformation.CaptionHeight));
    10        if (captionRect.Contains(location)) {
    11            NativeMethods.SetWindowLong(this.Handle, KeyboardConstaint.GWL_EXSTYLE,
    12                (int)NativeMethods.GetWindowLong(this.Handle, KeyboardConstaint.GWL_EXSTYLE)
    13                 & (~KeyboardConstaint.WS_DISABLED));
    14            NativeMethods.SendMessage(this.Handle, KeyboardConstaint.WM_SETFOCUS, IntPtr.Zero, IntPtr.Zero);
    15        } else {
    16            NativeMethods.SetWindowLong(this.Handle, KeyboardConstaint.GWL_EXSTYLE,
    17                (int)NativeMethods.GetWindowLong(this.Handle, KeyboardConstaint.GWL_EXSTYLE) | 
    18                 KeyboardConstaint.WS_DISABLED);
    19        }
    20    }
    21}

    鼠标单击标题栏,让屏幕键盘可以接收焦点,并激活,单击其他部分则不激活窗体(如果激活了,其他程序必然取消激活,
    输入就无法进行了),这样才可以进行输入,并且保证了可以拖动窗体到其他位置。

    至此,一个屏幕键盘程序差不多完成了,能够实现与实际键盘完全同步。至于窗体,按键重绘,以及 Num Lock, Caps Lock, 
    Scroll Lock 等键盘灯的模拟,这里就不讲了,如果有兴趣,可以下载完整的代码。最后我们的屏幕键盘程序运行的效果如
    下图:

    点击下载完整源代码

  • 相关阅读:
    PHP实现无限极分类
    html2canvas生成并下载图片
    一次线上问题引发的过程回顾和思考,以更换两台服务器结束
    Intellij IDEA启动项目报Command line is too long. Shorten command line for XXXApplication or also for
    mq 消费消息 与发送消息传参问题
    idea 创建不了 java 文件
    Java switch 中如何使用枚举?
    Collections排序
    在idea 设置 git 的用户名
    mongodb添加字段和创建自增主键
  • 原文地址:https://www.cnblogs.com/yzl050819/p/4013559.html
Copyright © 2020-2023  润新知