今回作るプログラムでは、右下にもうひとつエディットコントロールが
増えて、ここに選択候補を表示するようにします。
また、現在の選択候補を右上のコントロールに表示するようにもします。
(この例ではIMEは株式会社ジャストシステムのATOK13を使用しています。)
選択候補を取得するにはImmGetCandidateList関数を使います。
どのようなときに取得すればよいかというと、IMEが選択候補ウィンドウを
開いたときです。これは、WM_IME_NOTIFYメッセージでwParam値がIMN_OPENCANDIDATE
の時です。
hImcは入力コンテキストのハンドルです。DWORD ImmGetCandidateList( HIMC hIMC, DWORD dwIndex, LPCANDIDATELIST lpCandList, DWORD dwBufLen );
dwIndexは、候補リストのインデックス番号(0から始まる)です。 (ほとんどの場合候補リストは1つしかありません。)
lpCandListは、候補リストを取得するCANDIDATELIST構造体へのポインタです。
dwBufLenは、バッファのサイズを指定します。これを0にすると完全な候補リストを 受け取るに必要なバイト数を返します。
関数が失敗すると0を返します。
さて、CANDIDATELIST構造体は、次のように定義されています。
dwSizeは、この構造体のサイズを示します。typedef struct tagCANDIDATELIST { DWORD dwSize; DWORD dwStyle; DWORD dwCount; DWORD dwSelection; DWORD dwPageStart; DWORD dwPageSize; DWORD dwOffset[1]; } CANDIDATELIST, *PCANDIDATELIST;
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章参照)。
では、プログラムを見てみましょう。
リソース・スクリプトに変更はありません。
前章と大して変わりませんが、エディットコントロールが増えたので ID_IME3を新たにdefineしました。// 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;
また、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回 繰り返すとすべての候補文字列がわかります。
選択候補が変化したときも、同じようにして現在の選択候補文字列を知ることができます。
MyAddStr(hEdit2, "abc");を実行するとエディットコントロールhEdit2にabcという 文字列が付け加えられ、さらにスクロールして最新の行が表示されます。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; }
今回やったところは、わかってしまえばどうということもありませんが、ちょっとしたミスを犯すと 選択候補文字列が全く取得できなくなり、袋小路に陥ってしまう部分です。
Update 05/Aug/2000 By Y.Kumei