原文:2019-6-5-WPF-拼音输入法

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至254
public 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 win32
public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
static int hKeyboardHook = 0; //声明键盘钩子处理的初始值
public const int WH_KEYBOARD_LL = 13; //线程键盘钩子监听鼠标消息设为2,全局键盘监听鼠标消息设为13
HookProc KeyboardHookProcedure; //声明KeyboardHookProcedure作为HookProc类型 //键盘结构
[StructLayout(LayoutKind.Sequential)]
public class KeyboardHookStruct
{
public int vkCode; //定一个虚拟键码。该代码必须有一个价值的范围1至254
public 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;//KEYDOWN
private const int WM_KEYUP = 0x101;//KEYUP
private const int WM_SYSKEYDOWN = 0x104;//SYSKEYDOWN
private 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); #endregion public 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);
} #region
if (isLocked)
{
#region page
if (myKeyboardHookStruct.vkCode == 33)
{
OnPaged?.Invoke(-1);
}
if (myKeyboardHookStruct.vkCode == 34)
{
OnPaged?.Invoke(1);
}
#endregion if (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. WPF 拼音输入法

    原文:WPF 拼音输入法 本文来告诉大家如何使用 WPF 来写一个输入法,使用的方式是钩子. 目录 键盘 解析键盘 获得按键 输入流向 算法 实际上本文是在使用一个好用的软件 希沃白板 的时候发现在里 ...

  2. WPF与输入法冲突研究之一:百度输入法会导致WPF程序的崩溃!

    在学习和使用了WPF一段时间之后,有点感觉WPF是个不太成熟的框架,不知道是我学的太肤浅,还是WPF得BUG太多! >>>>>>>模拟场景<<&l ...

  3. 隐马尔科夫模型python实现简单拼音输入法

    在网上看到一篇关于隐马尔科夫模型的介绍,觉得简直不能再神奇,又在网上找到大神的一篇关于如何用隐马尔可夫模型实现中文拼音输入的博客,无奈大神没给可以运行的代码,只能纯手动网上找到了结巴分词的词库,根据此 ...

  4. Ubuntu 下ibus拼音输入法启用 (ubuntu 16.04

    Ubuntu 下ibus拼音输入法启用 我安装的是英文版的ubuntu 16.04,打开只带英文,并没有中文. 设置输入法为iBus 从system settings 进入language suppo ...

  5. JavaScript 汉字与拼音互转终极方案 附JS拼音输入法

    转:http://www.codeceo.com/article/javascript-pinyin.html 前言 网上关于JS实现汉字和拼音互转的文章很多,但是比较杂乱,都是互相抄来抄去,而且有的 ...

  6. 【干货】JS版汉字与拼音互转终极方案,附简单的JS拼音输入法

    前言 网上关于JS实现汉字和拼音互转的文章很多,但是比较杂乱,都是互相抄来抄去,而且有的不支持多音字,有的不支持声调,有的字典文件太大,还比如有时候我仅仅是需要获取汉字拼音首字母却要引入200kb的字 ...

  7. 用Lua扩展谷歌拼音输入法

    谷歌拼音输入法最后一次更新是2013年,最近2年毫无动静,这个产品应该已经停了,不过这并不影响对它的使用,我一直喜欢它的简洁和稳定. 说不上来什么原因,忽然想起了摆弄摆弄谷歌拼音输入法的扩展特性(我经 ...

  8. 评价qq拼音输入法

    我目前正在使用qq拼音输入法,从人机交互设计方面,我对qq输入法从用户界面.记住用户选择.短期刺激和长期使用的好处坏处.不让用户犯简单错误这四个方面进行了评价. 1.从用户界面方面: qq输入法用户界 ...

  9. 用C语言将搜狗输入法词库转换成QQ拼音输入法词库

    搜狗输入法词库格式: 'ni'kan'xia 你看下 'ni'kan'xia'gai'hou 你看下改后 'ni'kan'xing'ma 你看行吗 'ni'kan'zen'me'yang 你看怎么样 ...

  10. 评价正在使用输入法软件产品----QQ拼音输入法

    评价一下大家手头正在使用输入法或者搜索类的软件产品. 我现在使用的是系统自带的QQ拼音输入法,以前使用的是搜狗拼音输入法,后来发现可能由于我的系统重装过好几次,搜狗输入法也重装了好几次,而每次都删不干 ...

随机推荐

  1. [C++]Game模板-正面视角

    前言 本来是想打一个小游戏的-- 可是打到一半思路断了-- 只打出了模板--先把模板拿出来放着 Code //head #include <iostream> #include <c ...

  2. Net Core的API文档工具Swagger

    一.安装swagger 新建一个net core的api项目,通过NuGet安装Swashbuckle.AspNetCore. 二.注册swagger服务 在Startup.cs中注册Swagger生 ...

  3. Thinkphp 5.0.15 设计缺陷导致Insert/update-SQL注入 分析

    分析 与上一个漏洞类似,这个也是前端可以传入一个数组变量,如['exp','123','123'],后端根据array[0]来将array[1]和array[2]直接拼接到SQL语句中. 由于TP只是 ...

  4. IO相关Demo

    这几天复习了IO相关知识 只为记录,好记性不如烂笔头 有误请指正 ありがとうございます. 我的公众号 作者:晨钟暮鼓c个人微信公众号:程序猿的月光宝盒 1.判断存在,存在改名,并延迟删除,不存在新建 ...

  5. Add an Action with Option Selection 添加具有选项选择的按钮

    In this lesson, you will learn how to create an Action with support for option selection. A new View ...

  6. Implement Property Value Validation in Code 在代码中实现属性值验证(XPO)

    This lesson explains how to set rules for business classes and their properties. These rules are val ...

  7. flutter 下拉加载+下拉加载

    功能: 1.下拉加载 2.上拉加载 3.显示加载图标 4.更新列表数据,隐藏加载图标 flutter库: flutter_spinkit: ^3.1.0 加载图标 其他:加载列表需要列表,基于上一节的 ...

  8. vue中使用props传递参数

    通常,父组件的模板中包含子组件,父组件要正向地向子组件传递数据或参数,子组件收到后根据参数的不同来渲染不同的内容,或者执行操作. 这个正向传递数据的过程是通过props来实现的. 在组件中,子组件使用 ...

  9. Android Service 启动流程

    执行顺序 : startService -> bindService -> unbindService -> stopService 回调的结果为: 执行顺序 : startServ ...

  10. Linux:nginx服务器的搭建

    搭建nginx网站服务器 安装nginx yum install nginx 启动服务 安装完成后启动服务 systemctl start nginx 通过浏览器测试 地址栏输入localhost 不 ...