第282章 IMEの操作 その5


今回は、主にImmGetCadidateList関数の周辺についてやります。 一見簡単そうですが、ちょっと面倒なこともあります。



今回作るプログラムでは、右下にもうひとつエディットコントロールが 増えて、ここに選択候補を表示するようにします。 また、現在の選択候補を右上のコントロールに表示するようにもします。 (この例ではIMEは株式会社ジャストシステムのATOK13を使用しています。)



選択候補を取得するにはImmGetCandidateList関数を使います。 どのようなときに取得すればよいかというと、IMEが選択候補ウィンドウを 開いたときです。これは、WM_IME_NOTIFYメッセージでwParam値がIMN_OPENCANDIDATE の時です。

DWORD ImmGetCandidateList( HIMC hIMC, DWORD dwIndex, LPCANDIDATELIST lpCandList, DWORD dwBufLen );

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

dwIndexは、候補リストのインデックス番号(0から始まる)です。 (ほとんどの場合候補リストは1つしかありません。)

lpCandListは、候補リストを取得するCANDIDATELIST構造体へのポインタです。

dwBufLenは、バッファのサイズを指定します。これを0にすると完全な候補リストを 受け取るに必要なバイト数を返します。

関数が失敗すると0を返します。

さて、CANDIDATELIST構造体は、次のように定義されています。

typedef struct tagCANDIDATELIST { DWORD dwSize; DWORD dwStyle; DWORD dwCount; DWORD dwSelection; DWORD dwPageStart; DWORD dwPageSize; DWORD dwOffset[1]; } CANDIDATELIST, *PCANDIDATELIST;

dwSizeは、この構造体のサイズを示します。

dwStyleは、候補スタイルの値を示しています。これは、 次のうちの1つ以上の組み合わせです。

IME_CAND_UNKNOWN不明の候補スタイル
IME_CAND_READ候補は同一の読みです
IME_CAND_CODE 候補は同一の文字コード範囲にあります
IME_CAND_MEANING候補は同じ意味です
IME_CAND_RADICAL候補は同一の部首を使っています
IME_CAND_STROKES候補は同一の画数です

dwCountは、候補文字列の数です。

dwSelection は、候補文字列のうちの選択されているもののインデックスです。

dwPageStartは、候補ウィンドウ中の最初の候補文字列のインデックスです。

dwPageSizeは、候補ウィンドウ中の1ページに表示する候補文字列の数です。

dwOffsetは、最初の候補文字列のオフセット値です。

おそらく最後のメンバがわかりにくいと思います。選択候補文字列が10個有ったとすると 3番の候補文字列は、この構造体の最初よりdwOffset[2]バイトめより始まります。

現実的にはImmGetCandidateList(hImc, 0, NULL, 0);を実行して必要なサイズを取得して GlobalAlloc関数でメモリを確保しておきます。こういう使い方は、慣れないとちょっと まごついてしまいますが、Windowsのプログラミングでは時々必要となります。

BITMAPINFO構造体がこのような使い方をしています (第172章参照)。

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

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

// ime04.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 <windows.h> #include <windowsx.h> #include <imm.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyEditProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); int Err(HWND, char *); BOOL MyScroll(HWND); BOOL MyAddStr(HWND, char *); char szClassName[] = "ime04"; //ウィンドウクラス WNDPROC OrgEditProc; HWND hEdit2, hEdit3;

前章と大して変わりませんが、エディットコントロールが増えたので ID_IME3を新たにdefineしました。

また、MAX_EDIT_LENGTHも定義してみました。

プログラムを少し簡単にするためにMyScroll関数のプロトタイプの変更と 新たにMyAddStr関数を作ってみました。(後述)

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

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; HIMC hImc; static HWND hEdit; 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); 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); DestroyWindow(hEdit); DestroyWindow(hEdit2); DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }

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

WM_CREATEメッセージが来たら、3番目のエディットコントロールhEdit3を作ります。

WM_SIZEメッセージが来たら、3つのエディットコントロールの位置、大きさを調整します。 左のエディットコントロールの幅はは親のクライアント領域の半分です。右側のコントロールの 高さはクライアント領域の半分となっています。

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); } 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); break; } lpcdl = (LPCANDIDATELIST)GlobalLock(hMem); ImmGetCandidateList(hImc, 0, lpcdl, dwSize); MyAddStr(hEdit3, "\r\n[選択候補]\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); GlobalFree(hMem); break; case IMN_CHANGECANDIDATE: hImc = ImmGetContext(hWnd); dwSize = ImmGetCandidateList(hImc, 0, NULL, 0); hMem = GlobalAlloc(GHND, dwSize); if (hMem == NULL) { MessageBox(hWnd, "メモリが確保できません", "Error", MB_OK); 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); GlobalFree(hMem); ImmReleaseContext(hWnd, hImc); break; case IMN_OPENSTATUSWINDOW: MyAddStr(hEdit2, "ステータスウィンドウが開かれました\r\n"); break; case IMN_CLOSESTATUSWINDOW: MyAddStr(hEdit2, "ステータスウィンドウが閉じられました\r\n"); break; case IMN_SETCONVERSIONMODE: MyAddStr(hEdit2, "入力文字モードが変えられました\r\n"); break; case IMN_SETSENTENCEMODE: MyAddStr(hEdit2, "変換モードが変えられました\r\n"); break; } break; default: break; } return CallWindowProc(OrgEditProc, hWnd, msg, wp, lp); }

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

WM_IME_COMPOSITIONメッセージを受けてエディットコントロールに、編集文字列や 確定された文字列を表示する部分をMyAddStr関数を使って簡略化しました。

次に、WM_IME_NOTIFYメッセージが来て、wParam値がIMN_OPENCANDIDATEの時を 見てみましょう。
これは、候補ウィンドウが開いたときです。この時候補文字列を全部取得して右下の エディットコントロールに表示します。

LPCANDIDATELIST lpcdl;

であらかじめCANDIDATELIST構造体へのポインタ変数を定義しておきます。

ImmGetCandidateList(hImc, 0, NULL, 0);を実行して 必要なバイト数(dwSize)を求めます。GlobalAlloc関数でメモリを確保します。

最初の候補文字列はlpcdlからlpcdl->dwOffset[0]だけ後ろから始まります。 2番目の文字列はlpcdl->dwOffset[1]だけ後ろから始まります。これをlpcdl->dwCount回 繰り返すとすべての候補文字列がわかります。

選択候補が変化したときも、同じようにして現在の選択候補文字列を知ることができます。

BOOL MyScroll(HWND hWnd) { int iLine; iLine = Edit_GetLineCount(hWnd); SendMessage(hWnd, EM_LINESCROLL, 0, (LPARAM)iLine - 1); return TRUE; } BOOL MyAddStr(HWND hWnd, char *lpszStr) { char szAll[MAX_EDIT_LENGTH]; Edit_GetText(hWnd, szAll, MAX_EDIT_LENGTH); strcat(szAll, lpszStr); Edit_SetText(hWnd, szAll); MyScroll(hWnd); return TRUE; }

MyAddStr(hEdit2, "abc");を実行するとエディットコントロールhEdit2にabcという 文字列が付け加えられ、さらにスクロールして最新の行が表示されます。

今回やったところは、わかってしまえばどうということもありませんが、ちょっとしたミスを犯すと 選択候補文字列が全く取得できなくなり、袋小路に陥ってしまう部分です。


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

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