title author date CreateTime categories
WPF 拼音输入法
lindexi
2019-6-5 17:6:58 +0800
2018-4-30 11:5:18 +0800
输入法 WPF

本文来告诉大家如何使用 WPF 来写一个输入法,使用的方式是钩子。

实际上本文是在使用一个好用的软件 希沃白板 的时候发现在里面很难输入拼音来做课堂活动。

因为现在没有找到一个软件可以用来输入拼音的,快速的输入。输入音调是比较难的,所以我就重新做了一个输入法。

在yswenli的帮助,使用了yswenli/Wenli.IEM 方法做了一个输入法。

键盘

如果要做一个输入法,可以使用很多方法,本文使用的是全局 hook 的方式,需要注意,这个方式很容易让 360 杀掉。

注册钩子的方法很简单,只需要一个函数

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

关于 HookProc 请看代码

        public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);

所以简单的注册一个钩子只需要三行代码

                var keyboardHookProcedure = new HookProc(KeyboardHookProc);var hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboardHookProcedure, GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName), 0);private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam){// 如果你要算括号就有很多行}

那么拿到了 hook 可以如何使用,下面来告诉大家如何解析信息

解析键盘

解析的方式微软有说到,因为很简单,只需要定义一个结构,请看代码。定义的结构需要是这样的,不要去优化。

        [StructLayout(LayoutKind.Sequential)]public class KeyboardHookStruct{public int vkCode;  //定一个虚拟键码。该代码必须有一个价值的范围1至254public int scanCode; // 指定的硬件扫描码的关键public int flags;  // 键标志public int time; // 指定的时间戳记的这个讯息public int dwExtraInfo; // 指定额外信息相关的信息}

从参数就可以拿到,因为参数是指针,需要Marshal.PtrToStructure来拿到

 KeyboardHookStruct myKeyboardHookStruct = (KeyboardHookStruct) Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));

现在通过这个方法就可以拿到键盘的输入。

获得按键

虽然已经解析了,但是现在还是不知道用户按键是哪个。需要通过下面的方法转换,首先引用 WinForm ,因为定义在 WinForm 有,而且下面发送消息也是需要通过。

右击引用,点击程序集、框架,就可以看到 System.Windows.Forms ,请看图片

引用了之后就可以使用下面的方法拿到按键

Keys keyData = (System.Windows.Forms.Keys) myKeyboardHookStruct.vkCode;

尝试点一下,是不是就可以看到对应的值?

有了按键,那么下面如何写一个输入法就是需要使用了对应的算法了,如果想使用微软提供的算法,请看C# 输入法,我是需要用来输入拼音。所以下面来告诉大家如何从用户按键拿到用户想要的输入。

输入流向

虽然已经拿到了按键,但是拿到的按键还是需要转换字符串才可以处理

var key = keyData.ToString().ToLower();

现在的 key 就是一个字符串,在输入拼音,用户想的是快速的输入,而不是不停复制粘贴,对于普通的字符输入是可以直接输入,但是对于āáǎ的输入就无法直接输入的。

我看到一位老师是在记事本写了下面代码

āáǎà
ēéěè
īíǐì
ōóǒò
ūúǔù
ǖǘǚǜ

需要哪个就去复制哪个,如写 yùn 就需要输入 y 然后复制一下ù,然后写 n ,这样想输入连续的拼音是很慢的。

所以对于a,e,i,o,u,v才需要输入法转换,对于其他的就直接输入就好了。

那么如何让用户的按键无法直接输入到对应的程序,就需要使用下面的函数

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);

使用这个函数就可以通过信息钩子继续下一个钩子,在 private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam) 大家也看到有一个返回值,通过这个返回值可以告诉系统,是不是要把这个消息传给下一个程序。

如果返回的是 0 那么就是告诉系统,这个 hook 不处理,你需要把消息发给其他的程序。如果返回不是0 ,那么就是告诉系统,这个我处理了,其他的程序不能收到。

输入法判断用户输入的是 [a,z] [0,9] 告诉系统,不要发给其他的程序。

那么如果用户输入的不是 a,e,i,o,u,v 也就是可以直接给其他程序,这时怎么做?

实际上不管用户输入的是什么,只要发给其他程序都需要使用这个方法

System.Windows.Forms.SendKeys.SendWait(string str)

通过这个方法就会把 str 发送给当前用户输入的程序。

算法

现在可以拿到了全部的输入,而且知道了如何把转换的值发送给用户,大概一个输入法就是需要这两个。

如果从用户的输入知道用户需要的什么就是算法,下面使用的方法很简单。

判断用户输入的是不是a,e,i,o,u,v,发现不是就直接发送输入。如果是就不发送任何输入,让用户选需要的是哪个

这是我把它显示出来,代码还需要一个属性,表示当前是不是存在用户没有选的字符。如果存在,下一次输入的是[0,9]就是用户选的字符,因为只要5个可以选,对于大于5和0的就直接发送输入。当前对于现在很多输入法,都是按空格选第一个,这里也是需要判断用户输入的是不是空格。

在开发的时候发现还有很多的细节,不过这些我就不在这里告诉大家,我把代码放在下面,大家看一下。

我把程序放在论坛,可以点击 快速在课堂活动输入拼音 下载

源代码请看

       public MainWindow(){InitializeComponent();Loaded += MainWindow_Loaded;DataContext = this;Topmost = true;}private void HideVexogzPybnj(){LdefkgzYclfeufwx.Visibility = Visibility.Visible;KqhRst.Visibility = Visibility.Hidden;}private void MainWindow_Loaded(object sender, RoutedEventArgs e){HideVexogzPybnj();App.KeyBordHook.KeyUpEvent += KeyBordHook_KeyUpEvent;App.KeyBordHook.OnSpaced += KeyBordHook_OnSpaced;App.KeyBordHook.OnBacked += KeyBordHook_OnBacked;}private void KeyBordHook_OnBacked(){if (!_yuanyin){_key = "";App.KeyBordHook.IsStarted = false;return;}_yuanyin = false;HideVexogzPybnj();}private void KeyBordHook_OnSpaced(int choose){if (choose > 0){choose = choose - 1;}if (choose < 5){if (_yuanyin){App.KeyBordHook.Send(_yuanyinList[_key][choose].ToString());_yuanyin = false;HideVexogzPybnj();return;}}App.KeyBordHook.IsStarted = false;}private void KeyBordHook_KeyUpEvent(object sender, System.Windows.Forms.Keys e){var key = e.ToString().ToLower();List<string> yuanyin = "a,e,i,o,u,v".Split(',').ToList();if (yuanyin.Any(temp => temp == key)){if (_yuanyin){App.KeyBordHook.Send(_key);}_key = key;ShowDvfarVawmkezbi(_yuanyinList[key]);_yuanyin = true;}else{if (_yuanyin && !string.IsNullOrEmpty(_key)){_yuanyin = false;App.KeyBordHook.Send(_yuanyinList[_key][4].ToString());_key = "";HideVexogzPybnj();}App.KeyBordHook.Send(key);}}private string _key;private bool _yuanyin;private void ShowDvfarVawmkezbi(string str){StringBuilder temp = new StringBuilder();for (var i = 0; i < str.Length; i++){temp.Append((i + 1) + "." + str[i] + "    ");}Key = temp.ToString();KqhRst.Visibility = Visibility.Visible;LdefkgzYclfeufwx.Visibility = Visibility.Hidden;}public static readonly DependencyProperty KeyProperty = DependencyProperty.Register(nameof(Key), typeof(string), typeof(MainWindow), new PropertyMetadata(default(string)));public string Key{get => (string) GetValue(KeyProperty);set => SetValue(KeyProperty, value);}private readonly Dictionary<string, string> _yuanyinList = new Dictionary<string, string>(){{"a","āáǎàa" },{"e","ēéěèe" },{"i","īíǐìi" },{"o","ōóǒòo" },{"u","ūúǔùu" },{"v","ǖǘǚǜü" },};

    class KeyboardHook{#region win32public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);static int hKeyboardHook = 0; //声明键盘钩子处理的初始值public const int WH_KEYBOARD_LL = 13;   //线程键盘钩子监听鼠标消息设为2,全局键盘监听鼠标消息设为13HookProc KeyboardHookProcedure; //声明KeyboardHookProcedure作为HookProc类型//键盘结构[StructLayout(LayoutKind.Sequential)]public class KeyboardHookStruct{public int vkCode;  //定一个虚拟键码。该代码必须有一个价值的范围1至254public int scanCode; // 指定的硬件扫描码的关键public int flags;  // 键标志public int time; // 指定的时间戳记的这个讯息public int dwExtraInfo; // 指定额外信息相关的信息}[StructLayout(LayoutKind.Sequential)]public struct INPUT{public InputType dwType;public KEYBDINPUT ki;}[StructLayout(LayoutKind.Sequential, Pack = 1)]public struct KEYBDINPUT{public Int16 wVk;public Int16 wScan;public KEYEVENTF dwFlags;public Int32 time;public IntPtr dwExtraInfo;}public enum KEYEVENTF{EXTENDEDKEY = 1,KEYUP = 2,UNICODE = 4,SCANCODE = 8,}//使用此功能,安装了一个钩子[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);//调用此函数卸载钩子[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]public static extern bool UnhookWindowsHookEx(int idHook);//使用此功能,通过信息钩子继续下一个钩子[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);// 取得当前线程编号(线程钩子需要用到)[DllImport("kernel32.dll")]static extern int GetCurrentThreadId();//使用WINDOWS API函数代替获取当前实例的函数,防止钩子失效[DllImport("kernel32.dll")]public static extern IntPtr GetModuleHandle(string name);private const int WM_KEYDOWN = 0x100;//KEYDOWNprivate const int WM_KEYUP = 0x101;//KEYUPprivate const int WM_SYSKEYDOWN = 0x104;//SYSKEYDOWNprivate const int WM_SYSKEYUP = 0x105;//SYSKEYUP//ToAscii职能的转换指定的虚拟键码和键盘状态的相应字符或字符[DllImport("user32")]public static extern int ToAscii(int uVirtKey, //[in] 指定虚拟关键代码进行翻译。int uScanCode, // [in] 指定的硬件扫描码的关键须翻译成英文。高阶位的这个值设定的关键,如果是(不压)byte[] lpbKeyState, // [in] 指针,以256字节数组,包含当前键盘的状态。每个元素(字节)的数组包含状态的一个关键。如果高阶位的字节是一套,关键是下跌(按下)。在低比特,如果设置表明,关键是对切换。在此功能,只有肘位的CAPS LOCK键是相关的。在切换状态的NUM个锁和滚动锁定键被忽略。byte[] lpwTransKey, // [out] 指针的缓冲区收到翻译字符或字符。int fuState); // [in] Specifies whether a menu is active. This parameter must be 1 if a menu is active, or 0 otherwise.//获取按键的状态[DllImport("user32")]public static extern int GetKeyboardState(byte[] pbKeyState);[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]private static extern short GetKeyState(int vKey);[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]private static extern short GetAsyncKeyState(int vKey);[DllImport("user32.dll")]internal static extern uint SendInput(uint nInputs,[MarshalAs(UnmanagedType.LPArray), In] INPUT[] pInputs,int cbSize);#endregionpublic event EventHandler<Keys> KeyUpEvent;public event Action<int> OnSpaced;public event Action OnBacked;public event Action<int> OnPaged;public void Start(){// 安装键盘钩子if (hKeyboardHook == 0){KeyboardHookProcedure = new HookProc(KeyboardHookProc);hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName), 0);//如果SetWindowsHookEx失败if (hKeyboardHook == 0){Stop();throw new Exception("安装键盘钩子失败");}}}public void Stop(){bool retKeyboard = true;if (hKeyboardHook != 0){retKeyboard = UnhookWindowsHookEx(hKeyboardHook);hKeyboardHook = 0;}if (!retKeyboard){throw new Exception("卸载钩子失败!");}}public void Send(string msg){if (!string.IsNullOrEmpty(msg)){Stop();SendKeys.SendWait(msg);Start();}}bool isLocked = true;public bool IsStarted { set; get; }/// <summary>/// 按键处理/// </summary>/// <param name="nCode"></param>/// <param name="wParam"></param>/// <param name="lParam"></param>/// <returns>如果返回1,则结束消息,这个消息到此为止,不再传递。如果返回0或调用CallNextHookEx函数则消息出了这个钩子继续往下传递,也就是传给消息真正的接受者</returns>private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam){// 侦听键盘事件if (nCode >= 0 && wParam == 0x0100){KeyboardHookStruct myKeyboardHookStruct = (KeyboardHookStruct) Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));if (myKeyboardHookStruct.vkCode == 27){Environment.Exit(1);}#regionif (isLocked){#region pageif (myKeyboardHookStruct.vkCode == 33){OnPaged?.Invoke(-1);}if (myKeyboardHookStruct.vkCode == 34){OnPaged?.Invoke(1);}#endregionif (IsStarted && myKeyboardHookStruct.vkCode >= 48 && myKeyboardHookStruct.vkCode <= 57){var c = int.Parse(((char) myKeyboardHookStruct.vkCode).ToString());OnSpaced?.Invoke(c);IsStarted = false;return 1;}if (IsStarted && myKeyboardHookStruct.vkCode == 8){OnBacked?.Invoke();return IsStarted ? 1 : 0;}if ((myKeyboardHookStruct.vkCode >= 65 && myKeyboardHookStruct.vkCode <= 90) || myKeyboardHookStruct.vkCode == 32){if (myKeyboardHookStruct.vkCode >= 65 && myKeyboardHookStruct.vkCode <= 90){Keys keyData = (Keys) myKeyboardHookStruct.vkCode;KeyUpEvent?.Invoke(this, keyData);IsStarted = true;}if (myKeyboardHookStruct.vkCode == 32){IsStarted = true;OnSpaced?.Invoke(0);}return IsStarted ? 1 : 0;}else{return 0;}}#endregion}return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);}}

参见:C# 输入法

2019-6-5-WPF-拼音输入法相关推荐

  1. 输入法半角和全角的快捷转换_华宇拼音输入法 一款完全免费的国产输入法_第1页...

      华宇拼音输入法历史追溯   "华宇拼音输入法"是华宇自主研发的一款文字输入软件,其历史可以追溯到二十多年前.   1999年 华宇拼音输入法初入江湖,前身是李国华博士编写的&q ...

  2. 拼音输入法API开发解密---lua篇

    2019独角兽企业重金招聘Python工程师标准>>> 当今的拼音输入法太多,如搜狗拼音输入法,以及谷歌拼音输入法,都增加了输入法扩展功能,虽然竞争很激烈,但从技术上来说差不多. 先 ...

  3. 一个长期潜伏在微软拼音输入法中的Bug

    这个Bug不仅存在于微软拼音输入法2010中,也同样存在于Windows 8自带的微软拼音输入法2012中.更神奇的是,在谷歌拼音输入法V3.0.1.98中也出现了.值得欣慰的是,在百度输入法V2.0 ...

  4. xubuntu18.04安装Google拼音输入法

    安装小企鹅(fcitx) sudo apt install fcitx 安装google拼音输入法 sudo apt install fcitx-googlepinyin 用vim编辑xprofile ...

  5. 《数学之美》第21章 拼音输入法的数学原理

    1 输入法和编码 将一个方块形状的汉字输入到计算机中,本质上是一个将人为约定的信息记录编码--汉字,转换成计算机约定的编码(国际码或者UTF-8)的信息转换过程. 对汉字的编码分为两部分:对拼音的编码 ...

  6. Tails 3.13 发布,更新 Intel 微码,改进拼音输入法支持

    百度智能云 云生态狂欢季 热门云产品1折起>>>   Tails 3.13 发布了,Tails 是基于 Debian 的 Linux 发行版,可以帮助用户匿名使用互联网,几乎可以在任 ...

  7. 华宇输入法linux,华宇拼音输入法DEB版能切换为五笔输入法,附操作方法

    原紫光拼音现在叫华宇拼音输入法,DEB版兼容UOS.Deepin.Debian 10.5等操作系统,但是安装完后只有拼音而没有五笔输入法,以下是切换的方法,这样就可以实现拼音和五笔双用途了,随时都可切 ...

  8. 微软拼音输入法2007状态栏无法显示!

    可能有两种原因导致这种现象出现,如果你的操作系统是Windows XP SP2,那么系统的自启动项里有三项会和"微软拼音输入法2007"冲突.如果你的操作系统不是Windows X ...

  9. 软件战争中的小插曲:比较搜狗拼音和QQ拼音输入法

    用了好多年的搜狗拼音输入法也用了很长时间的QQ拼音输入法以及QQ拼音输入法纯净版.谷歌拼音输入法.等等,感受到搜狗的越来越卡和qq的越来越快,所以简单比较下两个公司对软件的发展理念,纯粹是个人看法,不 ...

  10. 智能ABC拼音输入法的“秘密”

    智能ABC拼音输入法的"秘密" 许多朋友在输入中文时,都使用智能ABC输入法,可是要想更快速.更方便,你就一定得了解其中"v"和"i"这两个 ...

最新文章

  1. 集线器、路由器与交换机
  2. tensorflow中的BN层实现
  3. java节假日api--关于节假日想到的
  4. java自动化初始变量_Java自动化测试-01.环境准备(JDK/环境变量/Intellij IDEA安装)
  5. Keras笔记(一)一些基本概念
  6. 协议地址结构_TCP/IP 协议 讲解
  7. 短网址PHP源码Shortny
  8. 如何实现一个优秀的散列表!
  9. php odbc 结果集处理,php常用ODBC函数集的简单示例
  10. P3244 [HNOI2015]落忆枫音
  11. Ubuntu -- 无法正常安装卸载ssh以及chattr无反应的问题
  12. SQL数据库注入防范 ASP.NET Globle警告
  13. 高以翔死因曝光!猝死前最后4分钟,他本还有一次活的机会...
  14. 【R语言 南丁格尔玫瑰图绘制】
  15. 计算机的内存大小有何作用,电脑内存用处有多大?你可能想不到!
  16. 再爆 Bug!Windows 11 任务栏、菜单栏无故消失,怎么解?
  17. win10自动聚焦无法更换壁纸
  18. 不使用第三个变量,交换两个变量值
  19. 基因测序与高通量测序区别
  20. Python解决:当文件夹存在时清空文件夹,文件夹不存在时新建文件夹

热门文章

  1. Nova 组件如何协同工作 - 每天5分钟玩转 OpenStack(24)
  2. A股各概念板块龙头股大全
  3. 百度杯”CTF比赛(十二月场)
  4. 第11章从 Web 抓取信息
  5. 最新发布:数据库防火墙技术市场调研报告
  6. rpm -qa的意思详解
  7. android root 升级失败怎么办,手机root失败怎么办 安卓手机root失败原因分析
  8. 图扑软件携手华为云再创合作共赢新局面
  9. Ubuntu 挂载ISO文件
  10. 一位程序员社畜的2021闲读书单!