欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C#实现输入法功能详解

程序员文章站 2022-06-29 19:38:29
虽说输入法不是什么新事物,各种语言版本都有,不过在c#不常见;这就会给人一种误会:c#不能做!其实c#能不能做呢,答案是肯定的——三种方式都行:imm、tsf以及外挂式。i...

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

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

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);

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

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基本上就可能实现鼠标键盘的监控或者锁定等;那么首先要安装钩子:

// 安装键盘钩子 
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.安装完后就要对获取到钩子进行处理:

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.停止钩子

public void stop()
  {
   bool retkeyboard = true;
   if (hkeyboardhook != 0)
   {
    retkeyboard = unhookwindowshookex(hkeyboardhook);
    hkeyboardhook = 0;
   }
   if (!(retkeyboard))
    throw new exception("卸载钩子失败!");
  }

4.注册事件

 private void wordboard_load(object sender, eventargs e)
   {
    program.keybordhook.keyupevent += keybordhook_keyupevent;
    program.keybordhook.onspaced += keybordhook_onspaced;
    program.keybordhook.onbacked += keybordhook_onbacked;
   }

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

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.显示输入

 private void keybordhook_keyupevent(object sender, keyeventargs e)
   {
    keys += e.keycode.tostring().tolower();
    this.showcharatar();
   }

7.空格上屏

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.将数据发送到激活的输入框中

public void send(string msg)
  {
   if (!string.isnullorempty(msg))
   {
    stop();
    sendkeys.send("{right}" + msg);
    start();
   }
  }

9.back键回退

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.键词转换

/*****************************************************************************************************
 * 本代码版权归@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#实现输入法功能详解

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