.NET TIPS

IMEで入力された日本語の読みを取得するには?[C#、VB]

デジタルアドバンテージ 岸本 真二郎
2009/05/14

 アプリケーションでユーザーに氏名や住所などを入力してもらう際、漢字での入力とともに、その“読み”もほしい場合がある。このような場合、わざわざ読みを入力してもらわなくとも、IMEに入力された読みを漢字入力が確定した時点でIMEから取得できる。

 例えば、ユーザーがIMEをONにしてテキストボックスに「にほんご」と入力し、変換候補として「日本語」が表示された後、確定キーを押した時点で、入力された「ニホンゴ」という文字列をIMEから取得できる(ただし、漢字入力が確定された直後にIMEをOFFにされると、読みの取得はできない)。

 このような処理は、IMEを制御するWin32 APIであるImmGetCompositionString関数を呼び出すことにより実現できるが、これをC#やVBから呼び出すには、いくつかの手順が必要になる。

 本稿では、漢字入力用と読み表示用の2つのテキストボックスを持つWindowsフォームを作成し、漢字入力が確定したら、その読みを表示するサンプル・アプリケーションを実装しながら、IMEから読みを取得する方法を紹介する。


サンプル・アプリケーションの実行画面
上側のテキストボックスに漢字を入力して確定すると、下側のテキストボックスに読みがセットされる。

 なお、本稿で作成しているサンプル・アプリケーションのVisual Studio 2008用プロジェクトは以下のリンクよりダウンロードできる。

 以降では、上記プロジェクトに含まれるソース・ファイルより主要な部分を抜き出して解説する。

Win32 APIの準備

 まずは、IME関係のWin32 APIをC#/VBから呼び出せるよう準備しておく。

private const int WM_CHAR = 0x102;
private const int WM_IME_COMPOSITION = 0x10F;
private const int GCS_RESULTREADSTR = 0x0200;

// コンテキスト・ハンドルの取得
[DllImport("Imm32.dll")]
private static extern int ImmGetContext(int hWnd);

// コンテキスト・ハンドルの解放
[DllImport("Imm32.dll")]
private static extern int ImmReleaseContext(int hWnd, int hIMC);

// IMEより読みなどの文字列を取得する
[DllImport("Imm32.dll")]
private static extern int ImmGetCompositionString(int hIMC, int dwIndex, StringBuilder lpBuf, int dwBufLen);

// IMEの状態取得
[DllImport("Imm32.dll")]
private static extern int ImmGetOpenStatus(int hIMC);
Private Const WM_CHAR As Integer = &H102
Private Const WM_IME_COMPOSITION As Integer = &H10F
Private Const GCS_RESULTREADSTR As Integer = &H200

' コンテキスト・ハンドルの取得
<DllImport("Imm32.dll")> _
Private Shared Function ImmGetContext(ByVal hWnd As Integer) As Integer
End Function

' コンテキスト・ハンドルの解放
<DllImport("Imm32.dll")> _
Private Shared Function ImmReleaseContext(ByVal hWnd As Integer, ByVal hIMC As Integer) As Integer
End Function

' IMEから読みなどの文字列を取得する
<DllImport("Imm32.dll")> _
Private Shared Function ImmGetCompositionString(ByVal hIMC As Integer, ByVal dwIndex As Integer, ByVal lpBuf As StringBuilder, ByVal dwBufLen As Integer) As Integer
End Function

' IMEの状態取得
<DllImport("Imm32.dll")> _
Private Shared Function ImmGetOpenStatus(ByVal hIMC As Integer) As Integer
End Function
Win32APIを呼び出す準備(上:C#、下:VB)

コントロールのサブクラス化と読みの取得

 IMEから読みを取得するには、IME関連のWindowsメッセージを監視する必要がある。これにはNativeWindowクラス(System.Windows.Forms名前空間)を使って、日本語が入力されるコントロールをサブクラス化し、コントロールに送られるWindowsメッセージをチェックする。具体的には、IMEでの漢字変換が確定すると「WM_IME_COMPOSITION」というメッセージが送られてくるので、これを監視する。

 ここではまず、MsgListenerというクラス名でNativeWindowクラスの派生クラスを作成し、コントロールをサブクラス化してWindowsメッセージの監視を行う。WndProcメソッドでWM_IME_COMPOSITIONメッセージ(0x10F)を受け取ったら、さらにメッセージのパラメータをチェックし、漢字の読みが確定したことを示す「GCS_RESULTREADSTR」が含まれる場合に、ImmGetCompositionString関数を使って読みの文字列を取得する。

 なおIME関連の処理を行う場合、最初にIMEの「コンテキスト・ハンドル」を取得(ImmGetContext関数)し、処理が終わったらコンテキスト・ハンドルを解放(ImmReleaseContext関数)する必要がある。

private class MsgListener : NativeWindow
{
  protected bool enabled = false; // メッセージの監視状態(On/Off)
  public delegate void Converted(System.Object sender, ConvertedEventArgs e);
  public event Converted OnConverted;

  // コンストラクタ
  public MsgListener(Control c) {
    AssignHandle(c.Handle);
    c.HandleDestroyed += new EventHandler(OnHandleDestroyed);
  }
  internal void OnHandleDestroyed(object sender, EventArgs e) {
    ReleaseHandle();
  }

  public bool Enabled {
    get { return enabled; }
    set { enabled = value; }
  }

  // 読み取得対象コントロールのウィンドウ・プロシージャ
  protected override void WndProc(ref System.Windows.Forms.Message m)
  {
    int hIMC;
    int intLength;

    if (enabled) {
      switch (m.Msg) {

        case WM_IME_COMPOSITION:
          string strYomi  = "";
          if (((uint)m.LParam & (uint)GCS_RESULTREADSTR) != 0) {
            hIMC = ImmGetContext(this.Handle.ToInt32());

            // 読みの文字列
            intLength = ImmGetCompositionString(
                            hIMC, GCS_RESULTREADSTR, null, 0);
            if (intLength > 0) {
              StringBuilder temp = new StringBuilder(intLength);
              ImmGetCompositionString(
                  hIMC, GCS_RESULTREADSTR, temp, intLength);
              strYomi = temp.ToString();
              if (strYomi.Length > intLength) {
                strYomi = strYomi.Substring(0, intLength);
              }

              // イベントを起動
              ConvertedEventArgs ea = new ConvertedEventArgs(
                                        strYomi, false, intLength);
              OnConverted(this, ea);
            }
            ImmReleaseContext(this.Handle.ToInt32(), hIMC);
          }
          break;

        case WM_CHAR:  // '半角英数字
          hIMC = ImmGetContext(this.Handle.ToInt32());

          if (ImmGetOpenStatus(hIMC) == 0) {
            char chr = Convert.ToChar(m.WParam.ToInt32() & 0xff);

            if (m.WParam.ToInt32() >= 32) {
              //イベント起動
              string str = chr.ToString();
              ConvertedEventArgs ea =
                          new ConvertedEventArgs(str, true, 1);
              OnConverted(this , ea);
            }
          }
          ImmReleaseContext(this.Handle.ToInt32(), hIMC);
          break;
      }
    }
    base.WndProc(ref m);
  }
}
Private Class MsgListener
  Inherits NativeWindow

  Protected m_enabled As Boolean = False ' メッセージの監視状態(On/Off)
  Public Delegate Sub Converted(ByVal sender As System.Object, ByVal e As ConvertedEventArgs)
  Public Event OnConverted As Converted

  ' コンストラクタ
  Public Sub New(ByVal c As Control)
    AssignHandle(c.Handle)
    AddHandler c.HandleDestroyed, AddressOf OnHandleDestroyed
  End Sub
  Friend Sub OnHandleDestroyed(ByVal sender As Object, _
    ByVal e As EventArgs)
    ReleaseHandle()
  End Sub

  Public Property Enabled() As Boolean
    Get
      Return m_enabled
    End Get
    Set(ByVal value As Boolean)
      m_enabled = value
    End Set
  End Property

  ' 読み取得対象コントロールのウィンドウ・プロシージャ
  Protected Overloads Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
    Dim hIMC As Integer
    Dim intLength As Integer

    If m_enabled Then
      Select Case m.Msg

        Case WM_IME_COMPOSITION
          Dim strResult As String = ""
          Dim strYomi As String = ""

          If (CUInt(m.LParam) And CUInt(GCS_RESULTREADSTR)) <> 0 Then
            hIMC = ImmGetContext(Me.Handle.ToInt32())

            ' 読みの文字列
            intLength = ImmGetCompositionString( _
                          hIMC, GCS_RESULTREADSTR, Nothing, 0)
            If intLength > 0 Then
              Dim temp As New StringBuilder(intLength)
              ImmGetCompositionString(_
                hIMC, GCS_RESULTREADSTR, temp, intLength)
              strYomi = temp.ToString()
              If strYomi.Length > intLength Then
                strYomi = strYomi.Substring(0, intLength)
              End If

              ' イベント起動
              Dim ea As New ConvertedEventArgs( _
                              strYomi, strResult, False, intLength)
              RaiseEvent OnConverted(Me, ea)
            End If
            ImmReleaseContext(Me.Handle.ToInt32(), hIMC)
          End If
          Exit Select

        Case WM_CHAR: '半角英数字
          hIMC = ImmGetContext(Me.Handle.ToInt32())

          If ImmGetOpenStatus(hIMC) = 0 Then
            Dim chr As Char = _
                    Convert.ToChar(m.WParam.ToInt32() And &HFF)

            If m.WParam.ToInt32() >= 32 Then
              'イベント起動
              Dim str As String = chr.ToString()
              Dim  ea As ConvertedEventArgs = _
                            new ConvertedEventArgs(str, true, 1)
              RaiseEvent OnConverted(Me, ea)
            End If
          End If
          ImmReleaseContext(Me.Handle.ToInt32(), hIMC)
          Exit Select
      End Select
    End If
    MyBase.WndProc(m)
  End Sub
End Class
IMEのメッセージを処理するWinProcメソッド(上:C#、下:VB)

 またこのMsgListenerクラスでは、読みを取得できた際に、あらかじめ登録されたメソッドを呼び出せるように、OnConvertedイベントを実装している。

読みを取得するメイン・クラス

 OnConvertedイベントに使用するイベント引数のクラスと、読みを取得するメインのクラスであるImeYomiConversionクラスは次のようになる。

 ImeYomiConversionクラスでは、EventConvertedメソッドをOnConvertedイベントに登録している。これにより、読みの取得が完了したら、EventConvertedメソッドが呼び出され、IMEから取得した読みをテキストボックスにセットできる。

// イベント引数
public class ConvertedEventArgs : EventArgs
{
  private string yomi; // 読み
  private bool bSingle; // 半角文字か
  private int yomiLength = 0; // 読みの文字数

  public ConvertedEventArgs(string f, bool singleFlag, int friLen) {
    yomi = f;
    bSingle = singleFlag;
    yomiLength = friLen;
  }

  public string YomiString {
    get { return yomi; }
  }

  public int YomiLen {
    get { return yomiLength; }
  }

  public bool IsSingleChar {
    get { return bSingle; }
  }
}

// 読みを取得するクラス
public class ImeYomiConversion
{
  // targetCtrl:日本語入力を行うコントロール
  // yomiCtrl:読みをセットするコントロール
  public ImeYomiConversion(Control targetCtrl, Control yomiCtrl)
  {
    targetControl = targetCtrl;
    this.targetYomiControl = yomiCtrl;

    MsgListener = new MsgListener(targetCtrl); // サブクラス化

    // 日本語入力が確定したらイベントで通知
    MsgListener.OnConverted +=
        new MsgListener.Converted(EventConverted);
  }

  public bool Enabled {
    get { return MsgListener.Enabled;} // メッセージ監視のOn/Off
    set { MsgListener.Enabled = value;} // メッセージ監視状態
  }

  // 変換処理が行われた
  private void EventConverted(object sender, ConvertedEventArgs ea)
  {
    if (TargetControl.Text.Length == 0) {
      // 漢字入力のコントロールが空なら読みも空に
      targetYomiControl.Text = "";
    }

    string s = "";
    if (ea.IsSingleChar) {
      s = ea.FuriganaString;
    } else {
      s = Strings.StrConv(ea.FuriganaString, VbStrConv.Wide, 0x411);
    }
    // 読みをコントロールにセットする
    targetYomiControl.Text += s;
  }
}
' イベント引数
Public Class ConvertedEventArgs
  Inherits EventArgs
  Private yomi As String ' 読み
  Private bSingle As Boolean ' 半角文字か
  Private yomiLength As Integer = 0 ' 読みの文字数
  Public Sub New(ByVal f As String, ByVal singleFlag As Boolean, ByVal friLen As Integer)
    yomi = f
    bSingle = singleFlag
    yomiLength = friLen
  End Sub
  Public ReadOnly Property YomiString() As String
    Get
      Return yomi
    End Get
  End Property
  Public ReadOnly Property YomiLen() As Integer
    Get
      Return yomiLength
    End Get
  End Property

  Public ReadOnly Property IsSingleChar() As Boolean
    Get
      Return bSingle
    End Get
  End Property
End Class

' 読みを取得するクラス
Public Class ImeYomiConversion

  ' targetCtrl:日本語入力を行うコントロール
  ' yomiCtrl:読みをセットするコントロール
  Public Sub New(ByVal targetCtrl As Control, ByVal yomiCtrl As Control)
    targetControl = targetCtrl
    Me.targetYomiControl = yomiCtrl

    listner = New MsgListener(targetControl) ' サブクラス化

    '日本語入力が確定したらイベントで通知
    AddHandler listner.OnConverted, AddressOf EventConverted
  End Sub

  Public Property Enabled() As Boolean
    ' メッセージ監視のOn/Off
    Get
      Return listner.Enabled
    End Get
    ' メッセージ監視状態
    Set(ByVal value As Boolean)
      listner.Enabled = value
    End Set
  End Property

  ' 変換処理が行われた
  Private Sub EventConverted(ByVal sender As Object, ByVal ea As ConvertedEventArgs)
    If targetControl.Text.Length = 0 Then
      ' 漢字入力のコントロールが空なら読みも空に
      targetYomiControl.Text = ""
    End If

    Dim s As String = ""
    If ea.IsSingleChar Then
      s = ea.FuriganaString
    Else
      s = Strings.StrConv(ea.FuriganaString, VbStrConv.Wide, &H411)
    End If
    ' 読みをコントロールにセットする
    targetYomiControl.Text += s
  End Sub
End Class
コントロールのサブクラス化とイベントの設定(上:C#、下:VB)
IMEから取得した読みは半角カタカナの文字列なので、VBのライブラリを使って全角カタカナに変換している。

利用例

 以上の処理を行うことで、IMEで入力した漢字に対応する読みを取得できるようになる。次に、2つのテキストボックスを配置したWindowsフォーム・アプリケーションで、上記のImeYomiConversionクラスを使って読みを取得する例を示す。

 ここでは、ImeYomiConversionクラスのインスタンスを生成し、漢字の入力を行うテキストボックスのフォーカス移動に応じて、読みの取得処理をOn/Offしている。

public class Form1 : System.Windows.Forms.Form
{
  private ImeYomiConversion yomiConv;

  private void Form1_Load(object sender, System.EventArgs e) {
    yomiConv = new ImeYomiConversion(this.TextBox1, this.TextBox2);
  }

  private void TextBox1_Enter(object sender, System.EventArgs e) {
    yomiConv.Enabled = true;
  }

  private void TextBox1_Leave(object sender, System.EventArgs e) {
    yomiConv.Enabled = false;
  }
}
Public Class Form1

  private yomiConv As ImeComposition.ImeYomiConversion

  Private Sub Form1_Load(ByVal sender As System.Object,  _
    ByVal e As System.EventArgs) Handles MyBase.Load
    yomiConv = new ImeComposition.ImeYomiConversion(TextBox1, _
    TextBox2)
  End Sub

 Private Sub TextBox1_Enter(ByVal sender As System.Object,  _
    ByVal e As System.EventArgs) Handles TextBox1.Enter
    yomiConv.Enabled = true
  End Sub

  Private Sub TextBox1_Leave(ByVal sender As System.Object,  _
    ByVal e As System.EventArgs) Handles TextBox1.Leave
    yomiConv.Enabled = false
  End Sub
End Class
Formクラスの処理(上:C#、下:VB)

 このアプリケーションの実行画面は冒頭で示したとおりだ。

 以上、少々コードが長くなってしまったが、行っていることはそれほど複雑ではない。ImmGetCompositionString関数は読み以外にも、変換により確定した文字列やユーザーが入力した内容(ローマ字入力なら英字の文字列)などの情報も取得できる。End of Article

カテゴリ:クラス・ライブラリ 処理対象:キーボード
カテゴリ:クラス・ライブラリ 処理対象:Win32 API
使用ライブラリ:NativeWindowクラス(System.Windows.Forms名前空間)

この記事と関連性の高い別の.NET TIPS
[ASP.NET]サーバ・コントロール上のIMEモードを制御するには?
このリストは、(株)デジタルアドバンテージが開発した
自動関連記事探索システム Jigsaw(ジグソー) により自動抽出したものです。
generated by

「.NET TIPS」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間