二月
15
2005

一个中文输入的类

1
作者:AirForce

一个中文输入的类

作 者:风戒

不想让IME(Input Method Editor)显示默认的窗口,只想用它的转换和选字功能看过“拿铁游戏论坛”上的一个兄弟的一些代码,修正了一些我认为的bug加入了一组控制函数,使得程序中可以显示一些按钮(button),玩家可以不必用热键就能切换输入法、全角/半角,中/英文标点。但我不知道这个程序能不能解决缩进的问题,程序如下:

#pragma comment ( lib, “imm32.lib” )
#include <windows.h>
#include <imm.h>

class CIme
{
bool g_bIme; // IME允许标志
char g_szCompStr[ MAX_PATH ]; // 存储转换后的串
char g_szCompReadStr[ MAX_PATH ]; // 存储输入的串
char g_szCandList[ MAX_PATH ]; // 存储整理成字符串选字表
int g_nImeCursor; // 存储转换后的串中的光标位置
CANDIDATELIST *g_lpCandList; // 存储标准的选字表
char g_szImeName[ 64 ]; // 存储输入法的名字
bool g_bImeSharp; // 全角标志
bool g_bImeSymbol; // 中文标点标志
void ConvertCandList( CANDIDATELIST *pCandList, char *pszCandList ); // 将选字表整理成串

public:
CIme():
g_lpCandList( NULL )
{
DisableIme();
// 通过DisableIme初始化一些数据
}
~CIme()
{
DisableIme();
if( g_lpCandList )
{
GlobalFree( (HANDLE)g_lpCandList );
g_lpCandList = NULL;
}
}

// 控制函数
void DisableIme(); // 关闭并禁止输入法,如ime已经打开则关闭,此后玩家不能用热键呼出IME
void EnableIme(); // 允许输入法,此后玩家可以用热键呼出IME
void NextIme(); // 切换到下一种输入法,必须EnableIme后才有效
void SharpIme( HWND hWnd ); // 切换全角/半角
void SymbolIme( HWND hWnd ); // 切换中/英文标点

// 状态函数
char* GetImeName(); // 得到输入法名字,如果当前是英文则返回NULL
bool IfImeSharp(); // 是否全角
bool IfImeSymbol(); // 是否中文标点
// 得到输入法状态,四个指针任意可为NULL则此状态不回返回

void GetImeInput(
char **pszCompStr,
// 在pszCompStr中返回转换后的串
char **pszCompReadStr, // 在pszCompReadStr中返回键盘直接输入的串
int *pnImeCursor, // 在pnImeCursor中返回szCompStr的光标位置
char **pszCandList //在pszCandList中返回选字表,每项之间以\t分隔
);

// 必须在消息中调用的函数,如果返回是true,则窗口函数应直接返回0,否则应传递给DefWindowProc
bool OnWM_INPUTLANGCHANGEREQUEST();
bool OnWM_INPUTLANGCHANGE( HWND hWnd );
bool OnWM_IME_SETCONTEXT(){ return true; }
bool OnWM_IME_STARTCOMPOSITION(){ return true; }
bool OnWM_IME_ENDCOMPOSITION(){ return true; }
bool OnWM_IME_NOTIFY( HWND hWnd, WPARAM wParam );
bool OnWM_IME_COMPOSITION( HWND hWnd, LPARAM lParam );
};

void CIme::DisableIme()
{
while( ImmIsIME( GetKeyboardLayout( 0 )))
ActivateKeyboardLayout(( HKL )HKL_NEXT, 0 ); // 如果IME打开通过循环切换到下一个关闭
g_bIme = false;
g_szCompStr[ 0 ] = 0;
g_szCompReadStr[ 0 ] = 0;
g_nImeCursor = 0;
g_szImeName[ 0 ] = 0;
g_szCandList[ 0 ] = 0;
}

void CIme::EnableIme()
{
g_bIme = true;
}

void CIme::NextIme()
{
if( !g_bIme )
return;
ActivateKeyboardLayout(( HKL )HKL_NEXT, 0 );
}

void CIme::SharpIme( HWND hWnd )
{
ImmSimulateHotKey( hWnd, IME_CHOTKEY_SHAPE_TOGGLE );
}

void CIme::SymbolIme( HWND hWnd )
{
ImmSimulateHotKey( hWnd, IME_CHOTKEY_SYMBOL_TOGGLE );
}

void CIme::ConvertCandList( CANDIDATELIST *pCandList, char *pszCandList )
{
// 转换CandidateList到一个串,\t分隔每一项
unsigned int i;
if( pCandList->dwCount < pCandList->dwSelection )
{
pszCandList[ 0 ] = 0;
return;
}

待选字序号超出总数,微软拼音第二次到选字表最后一页后再按PageDown会出现这种情况,并且会退出选字状态,开始一个新的输入。但微软拼音自己的IME窗口可以解决这个问题,估计微软拼音实现了更多的接口,所以使用了这种不太标准的数据。我现在无法解决这个问题,而且实际使用中也很少遇到这种事,而且其它标准输入法不会引起这种bug。非标准输入法估计实现的接口比较少,所以应该也不会引起这种缺陷。

for( i = 0; ( i < pCandList->dwCount – pCandList->dwSelection )&&( i < pCandList->dwPageSize ); i++ )
{
*pszCandList++ = ( i % 10 != 9 )? i % 10 + ’1′ : ’0′;
// 每项对应的数字键
*pszCandList++ = ‘.’; // 用’.'分隔
strcpy( pszCandList, (char*)pCandList + pCandList->dwOffset[ pCandList->dwSelection + i ] ); // 每项实际的内容
pszCandList += strlen( pszCandList );
*pszCandList++ = ‘\t’;
// 项之间以’\t’分隔
}
*( pszCandList – 1 )= 0;
// 串尾,并覆盖最后一个’\t’
}

bool CIme::OnWM_INPUTLANGCHANGEREQUEST()
{
return !g_bIme; // 如果禁止IME则返回false,此时窗口函数应返回0,否则DefWindowProc会打开输入法
}

bool CIme::OnWM_INPUTLANGCHANGE( HWND hWnd )
{
// IME改变
HKL hKL = GetKeyboardLayout( 0 );
if( ImmIsIME( hKL ))
{
HIMC hIMC = ImmGetContext( hWnd );
ImmEscape( hKL, hIMC, IME_ESC_IME_NAME, g_szImeName );
// 取得新输入法名字
DWORD dwConversion, dwSentence;
ImmGetConversionStatus( hIMC, &dwConversion, &dwSentence );
g_bImeSharp = ( dwConversion & IME_CMODE_FULLSHAPE )? true : false;
// 取得全角标志
g_bImeSymbol = ( dwConversion & IME_CMODE_SYMBOL )? true : false; // 取得中文标点标志
ImmReleaseContext( hWnd, hIMC );
}
else
// 英文输入
g_szImeName[ 0 ] = 0;
return false;
// 总是返回false,因为需要窗口函数调用DefWindowProc继续处理
}

bool CIme::OnWM_IME_NOTIFY( HWND hWnd, WPARAM wParam )
{
HIMC hIMC;
DWORD dwSize;
DWORD dwConversion, dwSentence;

switch( wParam )
{
case IMN_SETCONVERSIONMODE: // 全角/半角,中/英文标点改变
hIMC = ImmGetContext( hWnd );
ImmGetConversionStatus( hIMC, &dwConversion, &dwSentence );
g_bImeSharp = ( dwConversion & IME_CMODE_FULLSHAPE )? true : false;
g_bImeSymbol = ( dwConversion & IME_CMODE_SYMBOL )? true : false;
ImmReleaseContext( hWnd, hIMC );
break;

case IMN_OPENCANDIDATE: // 进入选字状态
case IMN_CHANGECANDIDATE: // 选字表翻页
hIMC = ImmGetContext( hWnd );
if( g_lpCandList )
{
GlobalFree( (HANDLE)g_lpCandList );
g_lpCandList = NULL;
}
// 释放以前的选字表
if( dwSize = ImmGetCandidateList( hIMC, 0, NULL, 0 ))
{
g_lpCandList = (LPCANDIDATELIST)GlobalAlloc( GPTR, dwSize );
if( g_lpCandList )
ImmGetCandidateList( hIMC, 0, g_lpCandList, dwSize );
}
// 得到新的选字表
ImmReleaseContext( hWnd, hIMC );
if( g_lpCandList )
ConvertCandList( g_lpCandList, g_szCandList );
// 选字表整理成串
break;

case IMN_CLOSECANDIDATE: // 关闭选字表
if( g_lpCandList )
{
GlobalFree( (HANDLE)g_lpCandList );
g_lpCandList = NULL;
}
// 释放
g_szCandList[ 0 ] = 0;
break;
}
return true;
// 总是返回true,防止IME窗口打开
}

bool CIme::OnWM_IME_COMPOSITION( HWND hWnd, LPARAM lParam )
{
// 输入改变
HIMC hIMC;
DWORD dwSize;
hIMC = ImmGetContext( hWnd );
if( lParam & GCS_COMPSTR )
{
dwSize = ImmGetCompositionString( hIMC, GCS_COMPSTR, (void*)g_szCompStr, sizeof( g_szCompStr ));
g_szCompStr[ dwSize ] = 0;
}
// 取得szCompStr
if( lParam & GCS_COMPREADSTR )
{
dwSize = ImmGetCompositionString( hIMC, GCS_COMPREADSTR, (void*)g_szCompReadStr, sizeof( g_szCompReadStr ));
g_szCompReadStr[ dwSize ] = 0;
}
// 取得szCompReadStr
if( lParam & GCS_CURSORPOS )
{
g_nImeCursor = 0xffff & ImmGetCompositionString( hIMC, GCS_CURSORPOS, NULL, 0 );
}
// 取得nImeCursor
if( lParam & GCS_RESULTSTR )
{
unsigned char str[ MAX_PATH ];
dwSize = ImmGetCompositionString( hIMC, GCS_RESULTSTR, (void*)str, sizeof( str ));
// 取得汉字输入串
str[ dwSize ] = 0;
unsigned char *p = str;

while( *p )PostMessage( hWnd, WM_CHAR, (WPARAM)(*p++), 1 ); // 转成WM_CHAR消息
}
ImmReleaseContext( hWnd, hIMC );
return true;
// 总是返回true,防止IME窗口打开
}

char* CIme::GetImeName()
{
return g_szImeName[ 0 ]? g_szImeName : NULL;
}

bool CIme::IfImeSharp()
{
// 是否全角
return g_bImeSharp;
}

bool CIme::IfImeSymbol()
{
// 是否中文标点
return g_bImeSymbol;
}

void CIme::GetImeInput( char **pszCompStr, char **pszCompReadStr, int *pnImeCursor, char **pszCandList )
{
if( pszCompStr )*pszCompStr = g_szCompStr;
if( pszCompReadStr )*pszCompReadStr = g_szCompReadStr;
if( pnImeCursor )*pnImeCursor = g_nImeCursor;
if( pszCandList )*pszCandList = g_szCandList;
}

由于微软拼音实现了很多自己的东西,CIme和它的兼容性有些问题:
1)在函数ConvertCandList中所说的选字表的问题。
2)函数GetImeInput返回的szCompReadStr显然经过了加工而不是最初的键盘输入。
它的每个可组合的输入占以空格补足的8字节(Byte),且新的不可组合的输入存为0xa1。我们可以在输入法名字中有子串“微软拼音”时,只显示末尾的一组8字节(Byte),如果有0xa1就什么都不显示,也可以直接用TextOut显示所有的。
编辑:请不要使用多线程下载,否则会被封IP。

本文附带的源程序(1.1MB)

随机日志

我要评论