2024年1月5日金曜日

WPF ComboBoxにおけるIMEの制御

いやー、WPFのTextBoxならIMEの制御を設定できるんですけど、ComboBox のEditableをFalseのままで、IsTextSearchEnabled="True"とか設定して、項目を[コード]:[名前]にした時にIMEをオフにしないと、コード入力が効かないんですわ。
いろいろ検索してみたんですが、まともなのが無かったんで、書きましたよ。はい。
public class Util
{

    [DllImport("imm32.dll")]
    public static extern IntPtr ImmGetContext(IntPtr hWnd);

    [DllImport("imm32.dll")]
    public static extern Boolean ImmReleaseContext(IntPtr hWnd);

    [DllImport("imm32.dll")]
    public static extern Boolean ImmSetConversionStatus(IntPtr hIMC, Int32 fdwConversion, Int32 fdwSentence);

    [DllImport("imm32.dll")]
    public static extern Boolean ImmSetOpenStatus(IntPtr hIMC, Int32 fOpen);

    [DllImport("imm32.dll")]
    public static extern Int32 ImmGetOpenStatus(IntPtr hIMC);

    [DllImport("imm32.dll")]
    public static extern Int32 ImmAssociateContext(IntPtr hWnd, Int32 hIMC);

    const Int32 IME_CMODE_ALPHANUMERIC = 0x0000;
    const Int32 IME_CMODE_NATIVE = 0x0001;
    const Int32 IME_CMODE_CHINESE = IME_CMODE_NATIVE;
    const Int32 IME_CMODE_HANGUL = IME_CMODE_NATIVE;
    const Int32 IME_CMODE_JAPANESE = IME_CMODE_NATIVE;
    const Int32 IME_CMODE_KATAKANA = 0x0002;  // only effect under IME_CMODE_NATIVE
    const Int32 IME_CMODE_LANGUAGE = 0x0003;
    const Int32 IME_CMODE_FULLSHAPE = 0x0008;
    const Int32 IME_CMODE_ROMAN = 0x0010;
    const Int32 IME_CMODE_CHARCODE = 0x0020;
    const Int32 IME_CMODE_HANJACONVERT = 0x0040;
    const Int32 IME_CMODE_NATIVESYMBOL = 0x0080;

    const Int32 IME_CMODE_SOFTKBD = 0x0080;
    const Int32 IME_CMODE_NOCONVERSION = 0x0100;
    const Int32 IME_CMODE_EUDC = 0x0200;
    const Int32 IME_CMODE_SYMBOL = 0x0400;
    const Int32 IME_CMODE_FIXED = 0x0800;


    const Int32 IME_CONFIG_GENERAL = 1;
    const Int32 IME_CONFIG_REGISTERWORD = 2;
    const Int32 IME_CONFIG_SELECTDICTIONARY = 3;

    const Int32 IME_SMODE_NONE = 0x0; // センテンスに関する情報はなし
    const Int32 IME_SMODE_PLURALCLAUSE = 0x01; // 変換のための複文情報を使う
    const Int32 IME_SMODE_SINGLECONVERT = 0x02; // 単漢字変換する
    const Int32 IME_SMODE_AUTOMATIC = 0x04; // 自動モードで変換
    const Int32 IME_SMODE_PHRASEPREDICT = 0x08; // 次の文字を予想するためにフレーズ情報を使う
    const Int32 IME_SMODE_CONVERSATION = 0x0010;

    /// <summary>
    /// IMEモード
    /// </summary>
    public enum ImeMode
    {
        Off = 1, // IME オフ
        Hiragana = 2, // 全角ひらがな
        Katakana = 3, // 全角カタカナ
        HalfKatakana = 4, // 半角カタカナ
        Alpha = 5, // 全角英数
        HalfAlpha = 6, // 半角英数
    };


    /// <summary>
    /// IMEを制御する。WPF ComboBox 等の一部コントロールでは、IMEの制御ができない。そのための対応。
    /// TextBox等では、下記で制御できる
    /// <TextBox InputMethod.IsInputMethodEnabled="False"/> IME無効
    /// <TextBox InputMethod.PreferredImeState="On" InputMethod.PreferredImeConversionMode="FullShape,Native"/> ひらがな
    /// <TextBox InputMethod.PreferredImeState="On" InputMethod.PreferredImeConversionMode="Katakana,FullShape"/> カタカナ
    /// <TextBox InputMethod.PreferredImeState="On" InputMethod.PreferredImeConversionMode="Katakana,Native"/> 半角カタカナ
    /// <TextBox InputMethod.PreferredImeState="On" InputMethod.PreferredImeConversionMode="Alphanumeric,FullShape"/> 全角英数
    /// <TextBox InputMethod.PreferredImeState="On" InputMethod.PreferredImeConversionMode="Alphanumeric"/> 半角英数
    /// </summary>
    /// <param name="ctrl"></param>
    /// <param name="mode"></param>
    public static void SetImeMode(Control ctrl, ImeMode mode)
    {
        IntPtr handle = IntPtr.Zero;
        HwndSource hwnd = PresentationSource.FromVisual(ctrl) as HwndSource;
        if (hwnd != null)
        {
            handle = hwnd.Handle;
        }
        IntPtr himc = ImmGetContext(handle);
        Int32 dwConversion = 0;

        try
        {
            if( mode == ImeMode.Off)
            {
                if (ImmGetOpenStatus(himc) != 0)
                {
                    ImmSetOpenStatus(himc, 0);
                }
                return;
            } else
            {
                if( ImmGetOpenStatus(himc) == 0)
                {
                    ImmSetOpenStatus(himc, 1);
                }
            }
            switch (mode)
            {
                case ImeMode.Hiragana:
                    ImmSetConversionStatus(himc, IME_CMODE_FULLSHAPE | IME_CMODE_JAPANESE, IME_SMODE_AUTOMATIC);
                    break;
                case ImeMode.Katakana:
                    ImmSetConversionStatus(himc, IME_CMODE_FULLSHAPE | IME_CMODE_LANGUAGE | IME_CMODE_KATAKANA, IME_SMODE_AUTOMATIC);
                    break;
                case ImeMode.HalfKatakana:
                    ImmSetConversionStatus(himc, IME_CMODE_LANGUAGE | IME_CMODE_KATAKANA, IME_SMODE_AUTOMATIC);
                    break;
                case ImeMode.Alpha:
                    ImmSetConversionStatus(himc, IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN, IME_SMODE_AUTOMATIC);
                    break;
                case ImeMode.HalfAlpha:
                    ImmSetConversionStatus(himc, IME_CMODE_ROMAN, IME_SMODE_AUTOMATIC);
                    break;
            }
        }
        finally
        {
            ImmReleaseContext(handle);
        }

    }

}
使い方は、GotFocusイベントをハンドリングして
   private void FooComboBox_GotFocus(object sender, RoutedEventArgs e)
   {
       Util.SetImeMode(sender as ComboBox, Util.ImeMode.Off);
   }
という感じ