昨日の続きです。
IMEでの日本語入力対応は昨日ので出来ると思いますが、問題はまだあります。
1つのゲーム画面内で入力を受け取る部分と受け取らない部分が混在している場合です。
チャットなどに日本語入力を使う場合、そういう状況は多分にあると思います。
そういう場合、正しくIMEを切り替えないと面倒なことになります。
入力を受け取らないところで日本語が入力されると、
勝手にデフォルトのコンポジションウィンドウが作られたりします。
それを放っておくと入力を受け取る部分にフォーカスを移しても、その処理が完了せず、
IMMから正しくメッセージが送られなかったりします。
下記のソースがそれに対応するための処理です。
<C++でのIMM系のAPIを使ったIMEの切り替えサンプルソース>
---------------------------------------------------------------------
//入力フォーカスを外すときの関数
void CIme::OnFocusOut(void) {
HIMC hImc = ImmGetContext(m_Handle);
//入力中の文字があれば、キャンセルしておきます
if (ImmGetCompositionString(hImc, GCS_COMPSTR, NULL, 0) > 0) {
ImmNotifyIME(hImc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
}
//復帰するときのために現在の状況を保存しておく
ImmGetConversionStatus(hImc, &m_Conversion, &m_Sentence);
m_Open = ImmGetOpenStatus(hImc) != 0;
ImmReleaseContext(m_Handle, hImc);
//IMEを"無効"にし、現在のIMCを保存しておきます
m_ImcReserve = ImmAssociateContext(m_Handle, NULL);
}
//入力フォーカスを受け取るときの関数
void CIme::OnFocusIn(void) {
//保存しておいたIMCで、IMEを有効にします
ImmAssociateContext(m_Handle, m_ImcReserve);
HIMC hImc = ImmGetContext(m_Handle);
//入力中の文字があれば、キャンセルしておきます
if (ImmGetCompositionString(hImc, GCS_COMPSTR, NULL, 0) > 0) {
ImmNotifyIME(hImc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
}
//前回保存しておいた状況を反映させます
if (m_Open) {
ImmSetOpenStatus(hImc, true);
ImmSetConversionStatus(hImc, m_Conversion, m_Sentence);
}
ImmReleaseContext(m_Handle, hImc);
}
---------------------------------------------------------------------
基本的に、チャットボックスなどをクリックしたときにOnFocusInを呼び、
その他のエリアをクリックしてフォーカスを外したときにOnFocusOutを呼ぶ、
という使い方を想定してあります。
これで入力フォーカスを持つときだけ、IMEを有効にして、
持たないときはIMEを無効にするという対応ができると思います。
IEとかだと入力フォーカスを受け取らないときに、
入力ロケールインジケーターのボタン等が無効になっているのですが、
これはどうやれば再現できるのでしょうか?
ImmSetOpenStatusでIMEをオフにするのとも、
ImmDisableIMEで無効にしているのとも違う気がするのですが...。
まだ、他にもIMEを操作する方法があるのでしょうか?
知っている方がいたら教えて欲しいです。
※2010/06/11 追記
ゲームで日本語入力を行おうと思うと、独自のエディットコントロールを作り、
IMEを適切に処理しなければなりません。
以前にもDXUTを使って取り掛かろうとして、一度挫折した機能。
かなり大変だったので、とりあえずIMEを処理する部分のソースだけでも晒しておきます。
そのうち、ちゃんとまとめて1コンテンツにしたいな。
ただし、フルスクリーンのゲームではないので、
入力コンポジションの描画処理は基本的にIMEに任せてあります。
フルスクリーンでの処理については、MSDNの方にぴったりな資料がありましたので参照してください
ゲームでの IME の使用
http://msdn.microsoft.com/ja-jp/library/bb206300%28VS.85%29.aspx
C++でのIMM系のAPIを使ったIMEを操作するソースコード
----------------------------------------------------------------------
//imm32.libのリンクとimm.hのインクルードを忘れずに
#pragma comment(lib, "imm32.lib")
#include "imm.h"
//ウィンドウに送られてくるIME関係のメッセージだけを受け取るプロシージャです。
//CImeはオリジナルクラスです。
int CIme::MessageProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static char buffer[BUFFER_MAX];
LRESULT result = 0;
switch (uMsg) {
//アプリケーションのウィンドウがアクティブになったときにIMMから送られてきます。
//lParamに描画する必要のあるウィンドウと描画しないウィンドウをIMEに通知するフラグが入っています
case WM_IME_SETCONTEXT:
//lParam = 0で描画をオフにできるが、フルスクリーンではないので描画はIMEにやってもらう
result = DefWindowProc(hWnd, uMsg, wParam, lParam);
break;
//ユーザーのキーストロークの結果として IME コンポジションを開始するときにIMMから送られてきます
case WM_IME_STARTCOMPOSITION:
//入力処理に対応するモノがあるかで対応を変える
if (SearchFocus()) {
//後々使うので、ハンドルを確保して、入力中のフラグを立てておく
m_Editing = true;
m_Handle = hWnd;
//入力コンテキストにアクセスするためのお約束
HIMC hImc = ImmGetContext(hWnd);
//コンポジションウィンドウの位置を設定
COMPOSITIONFORM info;
info.dwStyle = CFS_POINT;
info.ptCurrentPos.x = m_Focus->GetCaretX();
info.ptCurrentPos.y = m_Focus->GetCaretY();
ImmSetCompositionWindow(hImc, &info);
//コンポジションウィンドウのフォントを設定
ImmSetCompositionFont(hImc, m_Focus->GetFont()->GetInfoLog());
//入力コンテキストへのアクセスが終了したらロックを解除する
ImmReleaseContext(hWnd, hImc);
//STARTCOMPOSITION時の処理自体はIMEに任せる
result = DefWindowProc(hWnd, uMsg, wParam, lParam);
}else {
//入力処理を行いたくないときの対応です
HIMC hImc = ImmGetContext(hWnd);
//入力をキャンセルして、
ImmNotifyIME(hImc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
ImmReleaseContext(hWnd, hImc);
//IMEにはSTARTCOMPOSITIONが来たこと自体を伝えないようにします
return 0;
}
break;
//キーストロークによってコンポジションが変更されたときに送られます
//変更内容はlParamに取得できる情報の種類が入っています
case WM_IME_COMPOSITION:
//処理中でなければ、無視して、IMEにも伝えない
if (! m_Editing) return 0;
//GCS_RESULTSTR は文字列が確定したときに送られますので、
//以下で、確定文字列を受け取ります
if (lParam & GCS_RESULTSTR) {
HIMC hImc = ImmGetContext(hWnd);
//まずは文字列のサイズを確認
int len = ImmGetCompositionString(hImc, GCS_RESULTSTR, NULL, 0);
if (len >= BUFFER_MAX) len = BUFFER_MAX - 1;
//bufferにlen分の文字列を受け取ります
ImmGetCompositionString(hImc, GCS_RESULTSTR, buffer, len);
//受け取った文字列にはNULLがついていないので、終端をつけておきます
buffer[len] = '\0';
//受け取った文字列は御自由にw
m_Focus->AddText(buffer);
//※2010/06/11追記
//文字入力中に変換を行った後、エンターで入力を終了せず、
//そのままキー入力を続ける場合、コンポジションウィンドウの位置を変更しなければならない。
COMPOSITIONFORM info;
info.dwStyle = CFS_POINT;
info.ptCurrentPos.x = m_Focus->GetCaretX();
info.ptCurrentPos.y = m_Focus->GetCaretY();
ImmSetCompositionWindow(hImc, &info);
ImmReleaseContext(hWnd, hImc);
}
//文字列の横取りがすんだらIMEの方にも渡しておく
result = DefWindowProc(hWnd, uMsg, wParam, lParam);
break;
//IMEコンポジション操作の終了時に送られてきます
//ユーザーがEnterやEscを押したときですね
case WM_IME_ENDCOMPOSITION:
//基本的にすることはありません。
if (m_Editing) {
m_Editing = false;
}else {
//これはIMEに渡さなくてもいいかもしれません。が、いちよう渡しています
//return 0;
}
result = DefWindowProc(hWnd, uMsg, wParam, lParam);
break;
//確定した文字が1文字ずつ送られてくるらしいです
case WM_IME_CHAR:
//WM_IME_COMPOSITIONの方で横取りしているので無視してしまいます
return 0;
break;
}
return (int)result;
}
---------------------------------------------------------------------
ところどころにオリジナルの処理がありますが、雰囲気で理解してください。
m_Focusは独自のエディットコントロールへのポインタです。
これで、文字入力の日本語変換の機能をIMEに任せ、
確定した文字列を受け取って処理できるようになると思います。
※2010/06/07追記
実際にこれを使ってみると文字の変換を確定させた後、
変換候補ウィンドウが閉じるときなどに、ちらつきが見られた。
これはウィンドウが閉じるときにIMEからWM_ERASEBKGNDが送られて来ており、
これが原因でウィンドウが重なっていた部分を背景色で塗るため、ちらつくらしい。
対策としては、ウィンドウのメッセージループでWM_ERASEBKGNDを適切に処理すればいい。
独自の描画ルーチンを組んでいるのなら、DefWindowProcにまわさないようにするだけでいいと思う。
IME側の問題というわけでもないのですが、ちょっと躓いたので追記しておきます。
※2010/06/11追記
えーまた不具合がありました。
文字入力を行っているときに変換を行い、エンターキーを押して入力を終了せずに、
続いてキー入力をすると、WM_IME_STARTCOMPOSITIONが送られてこないようで、
コンポジションウィンドウの位置が最初の入力時の場所に表示されてしまい、
実際の入力位置とズレてしまうようです。
対策としては、WM_IME_COMPOSITIONで文字を受け取ったときに、
コンポジションウィンドウの位置を正しくキャレット位置に移動するようにします。