第283章 IMEの操作 その6


前章では選択候補が右下のエディットコントロールに表示されました。 せっかく表示されたのだから、ここをクリックして選択候補を選びたいものです。 今回は、これを実現します。また、右下のエディットコントロールを右クリックすることで IMEの「変換」を実現します。



入力コンテキストの状態変更をIMEに知らせるにはImmNotifyIME関数を使います。

BOOL ImmNotifyIME( HIMC hIMC, DWORD dwAction, DWORD dwIndex, DWORD dwValue );

hImcは、入力コンテキストハンドルです。

dwActionは、通知コードを指定します。次のなかから1つを選びます。

NI_CHANGECANDIDATELIST アプリケーションが現在の選択候補リストを変更しました。dwIndexには 選択候補リストのインデックスを指定します。dwValueは使いません。
NI_CLOSECANDIDATE アプリケーションは候補リストを閉じるように指示します。dwIndexには閉じる リストのインデックスを指定します。dwValueは使いません。IMEはアプリケーションに IMN_CLOSECANDIDATEが送られます
NI_COMPOSITIONSTR アプリケーションは編集文字列に対する操作を指示します。dwValueは使いません。 dwIndexは、CPS_CANCEL, CPS_COMPLETE, CPS_CONVERT, CPS_REVERTのいずれかを 指定します。
NI_IMEMENUSELECTED アプリケーションはIMEに特定のメニューをアプリケーションが操作することを 許可するよう指示します。dwIndexにはメニューのIDを、dwValueにはアプリケーション定義の メニュー項目の値を指定します。
NI_OPENCANDIDATE アプリケーションはIMEに候補リストをオープンするよう指示します。 dwIndexにはオープンするリストのインデックスを指定します。dwValueは使いません。 IMEはアプリケーションにIMN_OPENCANDIDATEを送ります。
NI_SELECTCANDIDATESTR アプリケーションは候補を選択します。dwIndexには候補リストのインデックス、 dwValueには候補文字列のインデックスを指定します。
NI_SETCANDIDATE_PAGESIZE アプリケーションは候補リストのページサイズを変更します。dwIndexには0から3の 範囲の候補リストを指定します。dwValueには、新しいページサイズを指定します。
NI_SETCANDIDATE_PAGESTART アプリケーションは候補リストのページ開始インデックスを変更します。 dwIndexには0から3の範囲で変更されるリストを指定します。dwValueには新しい ページの開始インデックスを指定します。

dwIndexには、dwActionがNI_COMPOSITIONSTRであれば、次のうちの1つを選択します。

CPS_CANCEL編集文字列をクリアして、編集文字列がないようにします。
CPS_COMPLETE編集文字列が確定したことにします。
CPS_CONVERT編集文字列を変換します。
CPS_REVERT現在の編集文字列をキャンセルして未変換文字列とします。

dwValueはdwActionの値によって意味が異なります。

関数が成功すると0以外の値を返します。失敗すると0を返します。

では、プログラムを見てみましょう。

リソース・スクリプトに変更はありません。

ime05.rcは第281章のime03.rcと全く同じです。

// ime05.cpp #ifndef STRICT #define STRICT #endif #define ID_IME 100 #define ID_IME2 101 #define ID_IME3 102 #define MAX_EDIT_LENGTH 1024 * 64 - 1 #include #include #include #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyEditProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyCandidateProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); int Err(HWND, char *); BOOL MyScroll(HWND); BOOL MyAddStr(HWND, char *); char szClassName[] = "ime05"; //ウィンドウクラス WNDPROC OrgEditProc; WNDPROC OrgCandidateProc; HWND hEdit, hEdit2, hEdit3;

右下のエディットコントロールでマウスのクリックを知る必要があるので これをサブクラス化します。サブクラス化されたウィンドウのプロシージャを MyCandidateProcとします。もともとプロシージャのアドレスはOrgCandidateProc に保存します。

また、プログラムの都合上、左のエディットコントロールのハンドルを グローバル変数(hEdit)にしました。

WinMain, InitApp, InitInstanceの各関数に変更はありません。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; HIMC hImc; HINSTANCE hInst; CREATESTRUCT *lpcs; DWORD dwConv, dwSent; HKL hKl; switch (msg) { case WM_CREATE: lpcs = (CREATESTRUCT *)lp; hInst = lpcs->hInstance; hEdit = CreateWindow("edit", "", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_HSCROLL | WS_VSCROLL, 0, 0, 0, 0, hWnd, (HMENU)ID_IME, hInst, NULL); hEdit2 = CreateWindow("edit", "", WS_CHILD | WS_BORDER | WS_BORDER | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_HSCROLL | WS_VSCROLL | ES_AUTOVSCROLL, 0, 0, 0, 0, hWnd, (HMENU)ID_IME2, hInst, NULL); hEdit3 = CreateWindow("edit", "", WS_CHILD | WS_BORDER | WS_BORDER | WS_VISIBLE | ES_MULTILINE | ES_WANTRETURN | WS_HSCROLL | WS_VSCROLL | ES_AUTOVSCROLL, 0, 0, 0, 0, hWnd, (HMENU)ID_IME3, hInst, NULL); OrgEditProc = (WNDPROC)GetWindowLong(hEdit, GWL_WNDPROC); SetWindowLong(hEdit, GWL_WNDPROC, (LONG)MyEditProc); OrgCandidateProc = (WNDPROC)GetWindowLong(hEdit3, GWL_WNDPROC); SetWindowLong(hEdit3, GWL_WNDPROC, (LONG)MyCandidateProc); SetFocus(hEdit); break; case WM_SIZE: MoveWindow(hEdit, 0, 0, LOWORD(lp) / 2, HIWORD(lp), TRUE); MoveWindow(hEdit2, LOWORD(lp) / 2, 0, LOWORD(lp) / 2, HIWORD(lp) / 2, TRUE); MoveWindow(hEdit3, LOWORD(lp) / 2, HIWORD(lp) / 2, LOWORD(lp) / 2, HIWORD(lp) / 2, TRUE); break; case WM_COMMAND: if (LOWORD(wp) == IDM_END) { SendMessage(hWnd, WM_CLOSE, 0, 0); return 0; } hImc = ImmGetContext(hEdit); ImmGetConversionStatus(hImc, &dwConv, &dwSent); switch (LOWORD(wp)) { case IDM_CONFIG: hKl = GetKeyboardLayout(0); ImmConfigureIME(hKl, hWnd, IME_CONFIG_GENERAL, NULL); break; case IDM_ONOFF: if (ImmGetOpenStatus(hImc)) { ImmSetOpenStatus(hImc, FALSE); } else { ImmSetOpenStatus(hImc, TRUE); } break; case IDM_CODE: //コード入力 if (ImmSetConversionStatus(hImc, IME_CMODE_CHARCODE, IME_SMODE_NONE) == 0) Err(hWnd, "ImmSetConversionStatus"); break; case IDM_ZENHIRA: //全角ひらかな if (ImmSetConversionStatus(hImc, IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE, dwSent) == 0) Err(hWnd, "ImmSetConversionStatus"); break; case IDM_ZENKATA: //全角カタカナ if (ImmSetConversionStatus(hImc, IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_KATAKANA, dwSent) == 0) Err(hWnd, "ImmSetConversionStatus"); break; case IDM_HANKATA: //半角カタカナ if (ImmSetConversionStatus(hImc, IME_CMODE_NATIVE | IME_CMODE_KATAKANA, dwSent) == 0) Err(hWnd, "ImmSetConversionStatus"); break; case IDM_ZENEISU: //全角英数 if (ImmSetConversionStatus(hImc, IME_CMODE_FULLSHAPE, dwSent) == 0) Err(hWnd, "ImmSetConversionStatus"); break; case IDM_HANEISU: //半角英数 if (ImmSetConversionStatus(hImc, IME_CMODE_ALPHANUMERIC, dwSent) == 0) Err(hWnd, "ImmSetConversionStatus"); break; case IDM_RENBUN: //連文節変換 if (ImmSetConversionStatus(hImc, dwConv, IME_SMODE_PHRASEPREDICT) == 0) Err(hWnd, "ImmSetConversionStatus"); break; case IDM_AUTO: //自動変換 if (ImmSetConversionStatus(hImc, dwConv, IME_SMODE_AUTOMATIC) == 0) Err(hWnd, "ImmSetConversionStatus"); break; case IDM_PLU: //複合語変換 if (ImmSetConversionStatus(hImc, dwConv, IME_SMODE_PLAURALCLAUSE) == 0) Err(hWnd, "ImmSetConversionStatus"); break; case IDM_CONV: //会話優先 if (ImmSetConversionStatus(hImc, dwConv, IME_SMODE_CONVERSATION) == 0) Err(hWnd, "ImmSetConversionStatus"); break; case IDM_NONCONV: //無変換(固定入力) if (ImmSetConversionStatus(hImc, dwConv, IME_SMODE_NONE) == 0) Err(hWnd, "ImmSetConversionStatus"); break; } SetFocus(hEdit); if (ImmReleaseContext(hEdit, hImc) == 0) Err(hEdit, "ImmReleaseConText"); break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { SetWindowLong(hEdit, GWL_WNDPROC, (LONG)OrgEditProc); SetWindowLong(hEdit3, GWL_WNDPROC, (LONG)OrgCandidateProc); DestroyWindow(hEdit); DestroyWindow(hEdit2); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }

親ウィンドウのプロシージャです。

いままで、ローカル変数だったhEditはなくなりました。(グローバル変数にしました)

WM_CREATEが来たら、右下のエディットコントロールをサブクラス化しています。

プログラム終了時にサブクラス化を解除します。

Err関数に変更はありません。

LRESULT CALLBACK MyEditProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { char szBuf[1024], szStr[1024]; HIMC hImc; LPCANDIDATELIST lpcdl; HGLOBAL hMem; DWORD dwSize; DWORD i; char szCandidate[64]; switch (msg) { case WM_CHAR: if (wp == (WPARAM)0x1b) { hImc = ImmGetContext(hWnd); if (ImmGetOpenStatus(hImc)) { ImmSetOpenStatus(hImc, FALSE); } else { ImmSetOpenStatus(hImc, TRUE); } } break; case WM_IME_COMPOSITION: hImc = ImmGetContext(hWnd); if (lp & GCS_RESULTSTR) { memset(szBuf, '\0', 1024); ImmGetCompositionString(hImc, GCS_RESULTSTR, szBuf, 1024); wsprintf(szStr, "「%s」を確定しました\r\n", szBuf); MyAddStr(hEdit2, szStr); Edit_SetText(hEdit3, ""); } if (lp & GCS_COMPSTR) { memset(szBuf, '\0', 1024); ImmGetCompositionString(hImc, GCS_COMPSTR, szBuf, 1024); wsprintf(szStr, "編集文字列は「%s」です。\r\n", szBuf); MyAddStr(hEdit2, szStr); } ImmReleaseContext(hWnd, hImc); break; case WM_IME_NOTIFY: switch (wp) { case IMN_OPENCANDIDATE: hImc = ImmGetContext(hWnd); dwSize = ImmGetCandidateList(hImc, 0, NULL, 0); hMem = GlobalAlloc(GHND, dwSize); if (hMem == NULL) { MessageBox(hWnd, "メモリーが確保できません", "Error", MB_OK); ImmReleaseContext(hWnd, hImc); break; } lpcdl = (LPCANDIDATELIST)GlobalLock(hMem); ImmGetCandidateList(hImc, 0, lpcdl, dwSize); Edit_SetText(hEdit3, "[選択候補]\r\n"); for (i = 0; i < lpcdl->dwCount; i++) { wsprintf(szCandidate, "%2d. %s\r\n", i + 1, (char *)lpcdl + lpcdl->dwOffset[i]); MyAddStr(hEdit3, szCandidate); } ImmReleaseContext(hWnd,hImc); GlobalUnlock(hMem); GlobalFree(hMem); return 0; case IMN_CHANGECANDIDATE: hImc = ImmGetContext(hWnd); dwSize = ImmGetCandidateList(hImc, 0, NULL, 0); hMem = GlobalAlloc(GHND, dwSize); if (hMem == NULL) { MessageBox(hWnd, "メモリが確保できません", "Error", MB_OK); ImmReleaseContext(hWnd, hImc); break; } lpcdl = (LPCANDIDATELIST)GlobalLock(hMem); ImmGetCandidateList(hImc, 0, lpcdl, dwSize); strcpy(szCandidate, (char *)lpcdl + lpcdl->dwOffset[lpcdl->dwSelection]); wsprintf(szBuf, "現在の選択候補は「%s」です\r\n", szCandidate); MyAddStr(hEdit2, szBuf); GlobalUnlock(hMem); GlobalFree(hMem); ImmReleaseContext(hWnd, hImc); return 0; case IMN_OPENSTATUSWINDOW: MyAddStr(hEdit2, "ステータスウィンドウが開かれました\r\n"); return 0; case IMN_CLOSESTATUSWINDOW: MyAddStr(hEdit2, "ステータスウィンドウが閉じられました\r\n"); return 0; case IMN_SETCONVERSIONMODE: MyAddStr(hEdit2, "入力文字モードが変えられました\r\n"); return 0; case IMN_SETSENTENCEMODE: MyAddStr(hEdit2, "変換モードが変えられました\r\n"); return 0; } break; default: break; } return CallWindowProc(OrgEditProc, hWnd, msg, wp, lp); }

サブクラス化された左のエディットコントロールのプロシージャです。

前章では右下のエディットコントロールに候補文字列を次々と表示しましたが、 今回の候補のみが表示されればよく、また、変換文字列が確定したら 何も表示する必要はありません。さらに、変換候補文字列の選択は エディットコントロールに表示されたものから選択するので 本来のIMEの候補ウィンドウも表示する必要はありません。

WM_IME_COMPOSITIONメッセージが来てなおかつwpにGCS_RESULTSTRが含まれる 場合(文字列が確定したとき)、右下のエディットコントロールをクリアします。 (Edit_SetText(hEdit3, "");)

WM_IME_NOTIFYメッセージが来てなおかつwpがIMN_OPNCANDIDATEの時(候補ウィンドウが オープンしたとき)、まず右下のエディットコントロールに「[選択候補]」と表示しますが 前章のようにこの前に改行を入れません。また、CallWindowProcにメッセージを渡さず すぐにreturn 0します。すると本来の候補ウィンドウは見えなくなります。

他の場合もすぐにreturnさせます。

MyScroll, MyAddStr関数に変更はありません。

// hEdit3のサブクラス化されたプロシージャ LRESULT CALLBACK MyCandidateProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { POINTL ptl; DWORD dwRet, dwSize; int nLine; HIMC hImc; CANDIDATELIST *lpcdl; HGLOBAL hMem; switch (msg) { case WM_LBUTTONDOWN: hImc = ImmGetContext(hEdit); dwSize = ImmGetCandidateList(hImc, 0, NULL, 0); hMem = GlobalAlloc(GHND, dwSize); if (hMem == NULL) { MessageBox(hWnd, "メモリが確保できません", "Error", MB_OK); ImmReleaseContext(hEdit, hImc); break; } lpcdl = (CANDIDATELIST *)GlobalLock(hMem); ImmGetCandidateList(hImc, 0, lpcdl, dwSize); ptl.x = LOWORD(lp); ptl.y = HIWORD(lp); dwRet = (DWORD)SendMessage(hWnd, EM_CHARFROMPOS, 0, (LPARAM)MAKELPARAM(ptl.x, ptl.y)); nLine = HIWORD(dwRet); if (nLine == 0 || nLine > (int)lpcdl->dwCount) { break; } GlobalUnlock(hMem); GlobalFree(hMem); ImmNotifyIME(hImc, NI_SELECTCANDIDATESTR, 0, nLine - 1); ImmReleaseContext(hEdit, hImc); return 0; case WM_RBUTTONDOWN: hImc = ImmGetContext(hEdit); ImmNotifyIME(hImc, NI_COMPOSITIONSTR, CPS_CONVERT, 0); ImmReleaseContext(hEdit, hImc); return 0; default: break; } return CallWindowProc(OrgCandidateProc, hWnd, msg, wp, lp); }

右下のエディットコントロールのサブクラス化されたプロシージャです。

マウスの左ボタンが押されたときに、押された場所を知りこの場所にある 選択候補文字列を知る必要があります。また、この文字列をImmNotifyIME関数で IMEに知らせます。

WM_LBUTTONDOWNメッセージが来たら、マウスのカーソルの座標を エディットコントロールに知らせて、何行目がクリックされたかを調べます。 0行目には[選択候補]の文字列があります。1行目には0番の選択候補文字列があり、 2行目には1番の選択候補文字列があります。

EM_CHARFROMPOS SendMessage( (HWND) hWnd, EM_CHARFROMPOS, (WPARAM) wParam, (LPARAM) lParam );

エディットコントロールのクライアント領域の特定の位置情報を取得します。 wParamは使われません。

lParam下位ワード値には水平座標、上位ワード値には垂直座標を指定します。

戻り値の下位ワード値にはキャラクターインデックス、上位ワード値には行 インデックスです。

クリックされた行がわかれば、選択候補文字列のインデックスがわかります。

選択候補文字列をImmNotifyIME関数でIMEに通知します。

WM_RBUTTONDOWNがきたら、やはりImmNotifyIME関数でCPS_CONVERTを指定して 変換します。

前章まではどちらかというと、IMEの変化をアプリケーションでとらえて、 これを表示するだけのプログラムでした。これで、やっとIMEの操作らしいことが できるようになりました。


[SDK第3部 Index] [総合Index] [Previous Chapter] [Next Chapter]

Update 11/Aug/2000 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。