C#实现输入法功能详解

时间:2022-02-15 04:47:32

虽说输入法不是什么新事物,各种语言版本都有,不过在c#不常见;这就会给人一种误会:c#不能做!其实c#能不能做呢,答案是肯定的——三种方式都行:imm、tsf以及外挂式。imm这种就是调windows的一些底层api,不过在新版本的windows中基本上已经不能用了,属于一种过时的操作方式。tsf是微软推荐的一种新方式,不过相对c#资料太少;线上主要的一些都是针对c++的版本资料,当然可以作为借鉴来实现c#版的。我这里主要介绍一种外挂式的(天啦撸,c#可以写外挂?),对于高手来说肯定不值一提,不过也算是实现了外挂及输入法!题外话——c#可以做外挂么?答案是可以的,c#针对windows的api编程资料还是很多的,下面就简单的介绍一下面可能要使用到的api:

安装了一个钩子,截取鼠标键盘等信号

?
1
public static extern int setwindowshookex(int idhook, hookproc lpfn, intptr hinstance, int threadid);

停止使用钩子

public static extern bool unhookwindowshookex(int idhook);

通过信息钩子继续下一个钩子

public static extern int callnexthookex(int idhook, int ncode, int32 wparam, intptr lparam);

线程钩子需要用到

static extern int getcurrentthreadid();

使用windows api函数代替获取当前实例的函数,防止钩子失效

public static extern intptr getmodulehandle(string name);

转换指定的虚拟键码和键盘状态的相应字符或字符

?
1
2
3
4
5
public static extern int toascii(int uvirtkey, //[in] 指定虚拟关键代码进行翻译。
int uscancode, // [in] 指定的硬件扫描码的关键须翻译成英文。高阶位的这个值设定的关键,如果是(不压)
byte[] lpbkeystate, // [in] 指针,以256字节数组,包含当前键盘的状态。每个元素(字节)的数组包含状态的一个关键。如果高阶位的字节是一套,关键是下跌(按下)。在低比特,如果设置表明,关键是对切换。在此功能,只有肘位的caps lock键是相关的。在切换状态的num个锁和滚动锁定键被忽略。
byte[] lpwtranskey, // [out] 指针的缓冲区收到翻译字符或字符。
int fustate);

1.有了以上的这些api基本上就可能实现鼠标键盘的监控或者锁定等;那么首先要安装钩子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 安装键盘钩子
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("安装键盘钩子失败");
    }
   }
  }

2.安装完后就要对获取到钩子进行处理:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
private int keyboardhookproc(int ncode, int32 wparam, intptr lparam)
  {
   // 侦听键盘事件
   if (ncode >= 0 && wparam == 0x0100)
   {
    keyboardhookstruct mykeyboardhookstruct = (keyboardhookstruct)marshal.ptrtostructure(lparam, typeof(keyboardhookstruct));
    #region 开关
    if (mykeyboardhookstruct.vkcode == 20 || mykeyboardhookstruct.vkcode == 160 || mykeyboardhookstruct.vkcode == 161)
    {
     islocked = islocked ? false : true;
    }
    #endregion
    #region
    if (islocked)
    {
     if (isstarted && mykeyboardhookstruct.vkcode >= 48 && mykeyboardhookstruct.vkcode <= 57)
     {
      var c = int.parse(((char)mykeyboardhookstruct.vkcode).tostring());
      onspaced(c);
      isstarted = false;
      return 1;
     }
     if (isstarted && mykeyboardhookstruct.vkcode == 8)
     {
      onbacked();
      return 1;
     }
     if ((mykeyboardhookstruct.vkcode >= 65 && mykeyboardhookstruct.vkcode <= 90) || mykeyboardhookstruct.vkcode == 32)
     {
      if (mykeyboardhookstruct.vkcode >= 65 && mykeyboardhookstruct.vkcode <= 90)
      {
       keys keydata = (keys)mykeyboardhookstruct.vkcode;
       keyeventargs e = new keyeventargs(keydata);
       keyupevent(this, e);
       isstarted = true;
      }
      if (mykeyboardhookstruct.vkcode == 32)
      {
       onspaced(0);
       isstarted = false;
      }
      return 1;
     }
     else
      return 0;
    }
    #endregion
   }
   return callnexthookex(hkeyboardhook, ncode, wparam, lparam);
  }

上面一些数字,对于刚入门的同学来说也不是什么问题,一看就明白是对哪些键做的操作。

3.停止钩子

?
1
2
3
4
5
6
7
8
9
10
11
public void stop()
  {
   bool retkeyboard = true;
   if (hkeyboardhook != 0)
   {
    retkeyboard = unhookwindowshookex(hkeyboardhook);
    hkeyboardhook = 0;
   }
   if (!(retkeyboard))
    throw new exception("卸载钩子失败!");
  }

4.注册事件

?
1
2
3
4
5
6
private void wordboard_load(object sender, eventargs e)
  {
   program.keybordhook.keyupevent += keybordhook_keyupevent;
   program.keybordhook.onspaced += keybordhook_onspaced;
   program.keybordhook.onbacked += keybordhook_onbacked;
  }

5.根据输入内容显示并进行转换

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void showcharatar()
  {
   this.listview1.begininvoke(new action(() =>
   {
    label1.text = keys;
    try
    {
     this.listview1.items.clear();
     var arr = cachehelper.get(keys);
     if (arr != null)
      for (int i = 0; i < (arr.length > 10 ? 9 : arr.length); i++)
      {
       this.listview1.items.add((i + 1) + "、" + arr[i]);
      }
    }
    catch
    {
     label1.text = keys = "";
    }
   }));
  }

6.显示输入

?
1
2
3
4
5
private void keybordhook_keyupevent(object sender, keyeventargs e)
  {
   keys += e.keycode.tostring().tolower();
   this.showcharatar();
  }

7.空格上屏

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void keybordhook_onspaced(int choose)
  {
   try
   {
    if (cachehelper.containskey(keys))
    {
     if (choose > 0)
     {
      choose = choose - 1;
     }
     program.keybordhook.send(cachehelper.get(keys)[choose]);
     label1.text = "";
     this.listview1.clear();
    }
   }
   catch
   {
   }
   keys = "";
  }

8.将数据发送到激活的输入框中

?
1
2
3
4
5
6
7
8
9
public void send(string msg)
  {
   if (!string.isnullorempty(msg))
   {
    stop();
    sendkeys.send("{right}" + msg);
    start();
   }
  }

9.back键回退

?
1
2
3
4
5
6
7
8
private void keybordhook_onbacked()
  {
   if (!string.isnullorempty(keys))
   {
    keys = keys.substring(0, keys.length - 1);
   }
   this.showcharatar();
  }

当然这里还可以使其他键来完善更多的功能,例如拼音的分页处理等

至于什么五笔、拼音就要使用词库来解决了;其中五笔比较简单,拼音就非常复杂了,各种分词、联想等...这里以五笔为主,拼音为单拼来实现基本的输入功能;所以不需要什么高深算法,简单使用memorycache就轻松高效搞定(有兴趣的可以来https://github.com/yswenli/wenli.iem 上完善)

10.键词转换

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/*****************************************************************************************************
 * 本代码版权归@wenli所有,all rights reserved (c) 2015-2017
*****************************************************************************************************
 * clr版本:4.0.30319.42000
 * 唯一标识:8ebc884b-ee5f-45de-8638-c054b832e0ce
 * 机器名称:wenli-pc
 * 联系人邮箱:wenguoli_520@qq.com
*****************************************************************************************************
 * 项目名称:$projectname$
 * 命名空间:wenli.iem
 * 类名称:cachehelper
 * 创建时间:2017/3/3 16:18:14
 * 创建人:wenli
 * 创建说明:
*****************************************************************************************************/
using system;
using system.collections.generic;
using system.io;
using system.linq;
using system.runtime.caching;
using system.text;
using system.windows.forms;
namespace wenli.iem.helper
{
 public static class cachehelper
 {
  static memorycache _wubicache = new memorycache("wubi");
  static memorycache _pinyincache = new memorycache("pinyin");
  static cachehelper()
  {
   var path = application.startuppath + "\\win32\\world.dll";
   var arr = file.readalllines(path);
   foreach (string item in arr)
   {
    var key = item.substring(0, item.indexof(" "));
    var value = item.substring(item.indexof(" ") + 1);
    _wubicache.add(key, (object)value, datetimeoffset.maxvalue);
   }
   //
   path = application.startuppath + "\\win32\\pinyin.dll";
   arr = file.readalllines(path);
   foreach (string item in arr)
   {
    var key = item.substring(0, item.indexof(" "));
    var value = item.substring(item.indexof(" ") + 1);
    _pinyincache.add(key, (object)value, datetimeoffset.maxvalue);
   }
  }
  public static string[] get(string key)
  {
   if (!string.isnullorempty(key))
   {
    var str = string.empty;
    try
    {
     if (_wubicache.contains(key))
      str = _wubicache[key].tostring();
    }
    catch { }
    try
    {
     if (_pinyincache.contains(key))
      str += " " + _pinyincache[key].tostring();
    }
    catch { }
    if (!string.isnullorempty(str))
    {
     var arr = str.split(new string[] { " " }, stringsplitoptions.removeemptyentries);
     for (int i = 0; i < arr.length; i++)
     {
      if (arr[i].indexof("*") > -1)
      {
       arr[i] = arr[i].substring(0, arr[i].indexof("*"));
      }
     }
     return arr;
    }
   }
   return null;
  }
  public static bool containskey(string key)
  {
   if (_wubicache.contains(key))
    return true;
   if (_pinyincache.contains(key))
    return true;
   return false;
  }
  public static void clear()
  {
   _wubicache.dispose();
   gc.collect(-1);
  }
 }
}

到此一个基本型的c#版外挂输入法就成功完成了,源码地址:https://github.com/yswenli/wenli.iem

C#实现输入法功能详解

C#实现输入法功能详解

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持服务器之家!

原文链接:http://www.cnblogs.com/yswenli/p/6528447.html