Programming Field - プログラミング Tips

再変換のやり方

既に入力したテキストを選択して「変換」キーを押すと、エディタによっては変換候補リストが出て、変換しなおすことが出来ます。これを「再変換」といいます。

プログラムの中で再変換をするには、再変換を行う入力フィールド(IME を所有するウィンドウ)で WM_IME_REQUEST メッセージを処理して、wParamIMR_RECONVERTION を捕らえ、その中で RECONVERTSTRING 構造体の処理などを行います。

ここで、文字列を Unicode で扱うときに注意しなければいけないことがあり、dwStrOffsetdwCompStrOffsetdwTargetStrOffset の各メンバは、全てバイト単位(文字数ではない)で長さを指定するということです。

また、RECONVERTSTRING の後ろに続く文字列データを、再変換したい選択範囲より多くしておき(例えば1行分など)、dwStrLen はそのデータ全ての長さ、dwCompStrLendwTargetStrLen は選択範囲のみの長さを指定して、まず ImmSetCompositionStringSCS_QUERYRECONVERTSTRING を指定して呼び出せば、IME 側が勝手に選択範囲を広げて dwCompStrLendwCompStrOffset に含めてくれます(ただし上記のバイト単位に注意)。このあと適切な選択範囲の調整を行って、再変換の本番を呼び出せば、Word にあるような再変換を行えます。

再変換の結果は WM_IME_COMPOSITION メッセージで取得します。

以下に RECONVERTSTRING の使い方を交えながら例を示します。

※ 独自エディター実装での使用を想定しており、ここでは記述を省略している関数も多くありますがご了承ください。

// 独自のエディタウィンドウのプロシージャ
LRESULT CALLBACK MyEditProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
         .
         .
         .

        // このメッセージは再変換に限らず普通の変換文字列を取得できる
        // 特に Unicode 文字の取得には必要
        case WM_IME_COMPOSITION:
            return OnIMEComposition(hWnd, wParam, lParam);
        case WM_IME_REQUEST:
        {
            // 再変換の呼び出しであれば実行
            if (PtrToInt(wParam) == IMR_RECONVERTSTRING)
                return DoReconvertion(hWnd, (LPRECONVERTSTRING) lParam);
        }
        break;
    }
    return DefWindowProc(hWnd, message, wParam, lParam);
}

// WM_IME_COMPOSITION を処理する関数
LRESULT OnIMEComposition(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    // 文字列が変換し終わったときかどうか
    if (lParam & GCS_RESULTSTR)
    {
        HIMC hIMC;
        LONG ln;
        LPWSTR lpBuffer;
        hIMC = ImmGetContext(hWnd);
        // 変換後の文字列の長さを取得する (戻り値はバイト単位)
        // (ImmGetCompositionStringW は Win98/Me/NT/2000/XP 以降で使用可能)
        ln = ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, NULL, 0);
        if (ln > 0)
        {
            // NULL 文字を含める
            ln += sizeof(WCHAR);
            lpBuffer = (LPWSTR) malloc(ln);
            memset(lpBuffer, 0, (size_t) ln);
            // 文字列を取得
            ImmGetCompositionStringW(hIMC, GCS_RESULTSTR, lpBuffer, (DWORD) ln);
            ImmReleaseContext(hWnd, hIMC);

            // 選択範囲に文字列の挿入を行うユーザ定義関数
            // (必要に応じて選択範囲も除去するように)
            MyInsertStringW(hWnd, lpBuffer);

            free(lpBuffer);
        }
        else
        {
            // 文字列は存在しないので終了する
            ImmReleaseContext(hWnd, hIMC);
            return DefWindowProc(hWnd, WM_IME_COMPOSITION, wParam, lParam);
        }
    }
    else
        return DefWindowProc(hWnd, WM_IME_COMPOSITION, wParam, lParam);
    return 0;
}

// 文字列入力中の小さいウィンドウの位置を設定する関数
void SetIMECompositionWindowPos(HWND hWnd, int x, int y)
{
    COMPOSITIONFORM cpsf;
    HIMC hIMC;
    cpsf.dwStyle = CFS_POINT;
    cpsf.ptCurrentPos.x = x;
    cpsf.ptCurrentPos.y = y;

    hIMC = ImmGetContext(hWnd);
    ImmSetCompositionWindow(hIMC, &cpsf);
    ImmReleaseContext(hWnd, hIMC);
}

// 再変換を行う関数
LRESULT DoReconvertion(HWND hWnd, LPRECONVERTSTRING lprcv)
{
    // 選択されている範囲をキープする変数
    DWORD dwSelStart, dwSelEnd;
    // 選択範囲が含まれる複数行の文字数をキープする変数
    // (dwStart は「選択範囲の開始点がある行」の頭に当たる位置(全体での位置)、
    //  dwEnd は「選択範囲の末尾がある行」の末尾の点)
    DWORD dwStart, dwEnd;
    DWORD dwLength, dwCSPos;
    HIMC hIMC;
    POINT ptCaret;

    // lprcv が NULL の場合、必要なバッファのサイズが求められている
    if (!lprcv)
    {
        // テキストの選択範囲を取得(ユーザ定義関数)
        MyGetSelRange(&dwSelStart, &dwSelEnd);
        // 選択範囲を含む行の範囲を取得(ユーザ定義関数)
        MyGetMultiLineRange(dwSelStart, dwSelEnd, &dwStart, &dwEnd);
        // 再変換を行う選択範囲と行の範囲をキープしておく(ユーザ定義関数)
        MySetReconvertRange(dwStart, dwEnd, dwSelStart, dwSelEnd);
    }

    // 必要なバッファのサイズを設定(NULL 文字も含める)
    dwLength = (sizeof(RECONVERTSTRING)
        + (dwSelEnd - dwSelStart + 1) * sizeof(WCHAR));
    if (!lprcv)
    {
        // バッファのサイズを返す
        return dwLength;
    }

    // キープした選択範囲と行の範囲取得する(ユーザ定義関数)
    MyGetReconvertRange(&dwStart, &dwEnd, &dwSelStart, &dwSelEnd);

    // lprcv が NULL でないとき、必要なデータをセットする
    // 以下の 2 つはすでにセットされている
    //lprcv->dwSize = sizeof(RECONVERTSTRING);
    //lprcv->dwVersion = 0;

    // dwStrOffset: lprcv のポインタの位置から何バイト先に
    //              文字列が存在するかを示す値
    lprcv->dwStrOffset = sizeof(RECONVERTSTRING);

    // dwStrLen: 再変換を行う文字列の長さ (NULL 文字は除く)
    lprcv->dwStrLen = dwEnd - dwStart;

    // dwCompStrOffset: 文字列の中で再変換の対象となる
    //                  文字の先頭位置(dwStrOffset から数えて)を示す値
    // (~Offset はバイト単位)
    lprcv->dwCompStrOffset = (dwSelStart - dwStart) * sizeof(WCHAR);

    // dwCompStrLen: 再変換の対象となる文字列の長さ
    lprcv->dwCompStrLen = dwSelEnd - dwSelStart;

    // dwTargetStrOffset, dwTargetStrLen:
    //   よく分からないので dwCompXXXX と同じにしておく^^;
    lprcv->dwTargetStrOffset = lprcv->dwCompStrOffset;
    lprcv->dwTargetStrLen = lprcv->dwCompStrLen;

    // 文字列をコピーするユーザ定義関数
    // (第3引数で指定したポインタに NULL 文字も含めてコピーする)
    MyGetRangeText(dwSelStart, dwSelEnd, (LPWSTR) (lprcv + 1));

    hIMC = ImmGetContext(hWnd);
    // 再変換の前処理を行う
    if (ImmSetCompositionStringW(hIMC, SCS_QUERYRECONVERTSTRING, lprcv, lprcv->dwSize, NULL, 0))
    {
        // 選択範囲の拡張があれば、必要に応じて処理する

        // バイト単位から文字列単位に戻す
        dwCSPos = lprcv->dwCompStrOffset / sizeof(WCHAR);
        if (dwCSPos != dwSelStart - dwStart ||
            lprcv->dwCompStrLen != dwSelEnd - dwSelStart)
        {
            dwSelStart = dwCSPos;
            dwSelEnd = dwSelStart + lprcv->dwCompStrLen;
            // 選択範囲を変更しておく
            MySetSelRange(dwSelStart, dwSelEnd);
        }
    }

    // 文字列の位置を座標に変換するユーザ定義関数
    MyGetTextCaretPos(dwSelStart, &ptCaret);

    // 変換ウィンドウの位置を調整する
    SetIMECompositionWindowPos(hWnd, ptCaret.x, ptCaret.y);

    if (!ImmSetCompositionStringW(hIMC, SCS_SETRECONVERTSTRING, lprcv, lprcv->dwSize, NULL, 0))
    {
        // 失敗したら 0 を返すようにする
        dwLength = 0;
    }
    ImmReleaseContext(hWnd, hIMC);
    // 成功した場合はバッファサイズを返す
    return dwLength;
}

最終更新日: 2006/07/28