読者です 読者をやめる 読者になる 読者になる

W-ZERO3 でテキストボックスにIMEで入力中であることを捕捉する

Dev .NET Mobile

IMEで入力しているかどうかはウィンドウメッセージで WM_IME_STARTCOMPOSITION がきたら入力開始、WM_IME_ENDCOMPOSITION が来たら入力終了とするのが基本。

しかし、ATOKの推測変換が入っている場合はいったん確定したあとに推測変換が更なる候補を提示している場合はすでに WM_IME_ENDCOMPOSITION が来た後なのでほかのアプローチが必要です。

で、調査してみた結果、「ATOKMConjecture」というウィンドウクラスのウィンドウが存在し、表示状態であれば、推測変換の一覧が表示中であるということがわかりました。

それをまとめたのが以下のクラス。コンストラクタに監視対象のテキストボックスを指定してインスタンス化すると、Conpositioning プロパティが true のときはIMEで変換中、または推測変換が内容を提示している状態になります。

2007/07/24(Tue) 追記 ウィンドウプロシージャの処理に誤りがありましたので修正しました。

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace EbiSoft.EbIRC
{
    /// <summary>
    /// テキストボックスのIME入力状態を監視するクラス
    /// </summary>
    class ImeInputFilter : IDisposable
    {
        #region P/Invoke 宣言

        [DllImport("coredll.dll")]
        private extern static IntPtr SetWindowLong(IntPtr hwnd, int nIndex, IntPtr dwNewLong);
        private const int GWL_WNDPROC = -4;

        [DllImport("coredll.dll")]
        private extern static int CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hwnd, uint msg, uint wParam, int lParam);

        [DllImport("coredll.dll")]
        public static extern IntPtr FindWindow(string className, string WindowsName);

        [DllImport("coredll.dll")]
        private static extern int IsWindowVisible(IntPtr hWnd);

        // IME変換のウィンドウメッセージ
        private const uint WM_IME_STARTCOMPOSITION = 0x10D; // IME変換開始
        private const uint WM_IME_ENDCOMPOSITION = 0x10E;   // IME変換終了

        // ATOK推測変換ウィンドウのウィンドウクラス
        private const string ATOK_CONJECTURE_CLASS = "ATOKMConjecture";

        #endregion

        /// <summary>
        /// サブクラス化するウィンドウプロシージャのデリゲート
        /// </summary>
        public delegate int WndProcDelegate(IntPtr hwnd, uint msg, uint wParam, int lParam);

        private bool disposed = false;    // Dispose が呼ばれたか
        private IntPtr oldWndProc;        // 前のハンドル
        private IntPtr targetHandle;      // テキストボックスのハンドル
        private WndProcDelegate wndProc;
        private IntPtr wndProcPtr;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="targetTextBox">処理対象のテキストボックス</param>
        public ImeInputFilter(TextBox targetTextBox)
        {
            targetHandle = targetTextBox.Handle;
            wndProc = new WndProcDelegate(WndProc);
            wndProcPtr = Marshal.GetFunctionPointerForDelegate(wndProc);

            // サブクラス化する
            oldWndProc = SetWindowLong(targetHandle, GWL_WNDPROC, wndProcPtr);
        }

        /// <summary>
        /// 破棄
        /// </summary>
        public void Dispose()
        {
            if (!disposed)
            {
                // サブクラス化解除する
                SetWindowLong(targetHandle, GWL_WNDPROC, oldWndProc);
                disposed = true;
            }
        }

        /// <summary>
        /// ウィンドウプロシージャ
        /// </summary>
        private int WndProc(IntPtr hwnd, uint msg, uint wParam, int lParam)
        {
            /* 2007/07/24(Thr) 修正 ------------------------------------------------------
            switch (msg)
            {
                case WM_IME_STARTCOMPOSITION:  // IME 変換開始
                    m_compositioning = true;
                    return 1;

                case WM_IME_ENDCOMPOSITION:    // IME 変換終了
                    m_compositioning = false;
                    return 1;

                default:
                    // デフォルトのプロシージャへ
                    return CallWindowProc(oldWndProc, hwnd, msg, wParam, lParam);
            }
            * -------------------------------------------------------------------------- */
            
            switch (msg)
            {
                case WM_IME_STARTCOMPOSITION:  // IME 変換開始
                    m_compositioning = true;
                    break;

                case WM_IME_ENDCOMPOSITION:    // IME 変換終了
                    m_compositioning = false;
                    break;
            }

            // デフォルトのプロシージャへ
            return CallWindowProc(oldWndProc, hwnd, msg, wParam, lParam);
        }

        /// <summary>
        /// ATOK推測変換がアクティブかどうか
        /// </summary>
        /// <returns>ATOK推測変換がアクティブなら true</returns>
        public bool IsAtokConjectureActive()
        {
            IntPtr atokPtr = FindWindow(ATOK_CONJECTURE_CLASS, null);

            if (atokPtr != IntPtr.Zero)
            {
                int hr = IsWindowVisible(atokPtr);
                return hr == 1;
            }
            else
            {
                return false;
            }
        }

        #region プロパティ

        /// <summary>
        /// 処理対象になっているテキストボックス
        /// </summary>
        public TextBox TextBox
        {
            get { return m_textBox; }
        }
        private TextBox m_textBox = null;

        /// <summary>
        /// 変換中かどうか
        /// </summary>
        public bool Conpositioning
        {
            get { return m_compositioning || IsAtokConjectureActive(); }
        }
        private bool m_compositioning = false;

        #endregion
    }
}