none
リッチテキストボックスでのIME入力中における上書きモードについて RRS feed

  • 質問

  • おつかれ様です。初めて投稿させていただきます。

    よろしくお願いいたします。

    現在Windows Formを使用した開発を行っており、

    以下の機能を有したテキストボックスが必要になりました。

    ・文字単位で文字色を変更できること。

    ・挿入モード、上書きモードでの入力ができること。

    上記機能を実現するため「RichTextBox」を選択して開発しているのですが、

    IME使用中における上書きモードでの入力ができず、頭を悩ませております。

    変換確定前の入力中の文字が元の文字列に挿入されていってしまいます。

    (TextBoxではIME入力中の上書きモードを実装できるのですが、文字単位での色変更ができないため使用できまえん。

    言語:C#

    .NET Framework:4.2.1

    RichTextBoxで上書きモードを実装する方法についてご存知の方がいらっしゃればご教授お願いいたします。

    よろしくお願いいたします。

    2015年7月12日 17:33

すべての返信

  • おそらくエミュレートするしかないんじゃないかと思います。以下、VBのサンプルですが、ご紹介しておきます。

    Visual Basic のテキスト ボックスで上書きモードをエミュレートする方法
    https://support.microsoft.com/en-us/kb/96210/ja


    ★良い回答には回答済みマークを付けよう! MVP - .NET  http://d.hatena.ne.jp/trapemiya/

    2015年7月13日 2:52
    モデレータ
  • お疲れ様です。

    解答していただきありがとうございます。

    また返事が遅くなり申し訳ありません。

    当方法について実験いたしましたが

    主に以下の点で思ったような動きが実現できませんでした。

    ・キャレットをバックするときに、Backキーを長押ししないと行けない。

    ・RichTextBoxではやはりIME使用中は文字が挿入されてします。

    他に方法がないかもう少し模索したいと思います。

    2015年7月16日 19:37
  • こんな?

    class RichTextBoxEx : RichTextBox
    {
        [DllImport("Imm32.dll", SetLastError = true)]
        static extern IntPtr ImmGetContext(IntPtr hWnd);
    
        [DllImport("imm32.dll", CharSet = CharSet.Unicode)]
        static extern int ImmGetCompositionString(IntPtr hIMC, GCS dwIndex, IntPtr pBuff, int dwBufLen);
    
        [DllImport("Imm32.dll")]
        static extern bool ImmReleaseContext(IntPtr hWnd, IntPtr hIMC);
    
        enum GCS
        {
            GCS_COMPREADSTR = 0x0001,
            GCS_COMPREADATTR = 0x0002,
            GCS_COMPREADCLAUSE = 0x0004,
            GCS_COMPSTR = 0x0008,
            GCS_COMPATTR = 0x0010,
            GCS_COMPCLAUSE = 0x0020,
            GCS_CURSORPOS = 0x0080,
            GCS_DELTASTART = 0x0100,
            GCS_RESULTREADSTR = 0x0200,
            GCS_RESULTREADCLAUSE = 0x0400,
            GCS_RESULTSTR = 0x0800,
            GCS_RESULTCLAUSE = 0x1000,
        }
        // attribute for COMPOSITIONSTRING Structure
        enum ATTR
        {
            ATTR_INPUT            =          0x00,
            ATTR_TARGET_CONVERTED  =         0x01,
            ATTR_CONVERTED          =        0x02,
            ATTR_TARGET_NOTCONVERTED =       0x03,
            ATTR_INPUT_ERROR          =      0x04,
            ATTR_FIXEDCONVERTED        =     0x05,
        }
    
        const int WM_SETREDRAW = 0x000B;
        const int WM_PAINT = 0x000F;
    
        private class ImmData
        {
            public string Composition;
            public int CursorPosition;
            public ATTR[] Attribute;
        }
    
        /// <summary></summary>
        /// <param name="isResult">trueは確定文字列を取り出す。falseは未確定文字列を取り出す</param>
        /// <param name="position">カーソル位置</param>
        /// <returns>取り出した文字列</returns>
        private ImmData GetIMEString(bool isResult)
        {
            ImmData immdata = new ImmData();
    
            IntPtr hIMC = ImmGetContext(this.Handle);
            if (hIMC != IntPtr.Zero)
            {
                try
                {
                    GCS gcs = isResult ? GCS.GCS_RESULTSTR : GCS.GCS_COMPSTR;
    
                    //変換中文字の属性
                    byte[] x = ImmGetCompositionData(hIMC, GCS.GCS_COMPATTR);
                    immdata.Attribute = new ATTR[x.Length];
                    for (int i = 0; i < x.Length; i++)
                    {
                        immdata.Attribute[i] = (ATTR)x[i];
                    }
    
                    var y = ImmGetCompositionData(hIMC, GCS.GCS_COMPREADATTR);
    
                    //カーソル位置を取得
                    immdata.CursorPosition = ImmGetCompositionString(hIMC, GCS.GCS_CURSORPOS, IntPtr.Zero, 0);
    
                    //文字列取出し
                    byte[] buff = ImmGetCompositionData(hIMC, isResult ? GCS.GCS_RESULTSTR : GCS.GCS_COMPSTR);
                    immdata.Composition = System.Text.Encoding.Unicode.GetString(buff);
                }
                finally
                {
                    ImmReleaseContext(this.Handle, hIMC);
                }
            }
            return immdata;
        }
        private byte[] ImmGetCompositionData(IntPtr hIMC, GCS gcs)
        {
            //文字バッファを用意
            int len = ImmGetCompositionString(hIMC, gcs, IntPtr.Zero, 0);
            byte[] buff = new byte[len];
            IntPtr pBuff = System.Runtime.InteropServices.Marshal.UnsafeAddrOfPinnedArrayElement(buff, 0);
    
            //文字列取出し
            ImmGetCompositionString(hIMC, gcs, pBuff, len);
            return buff;
        }
    
    
        int startPosition;//上書き開始位置
        string backupText;//上書きされている文字列
        string compositionText;//変換中文字列
        bool isImmProcessing;//IMMで操作中の描画ちらつきを止めるためのフラグ
    
        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
            case  WM_SETREDRAW:
            case WM_PAINT:
                if (isImmProcessing)
                {
                    return;
                }
                break;
            case 0x010D: //WM_IME_STARTCOMPOSITION
                {
                    //上書き開始位置を保存
                    startPosition = this.SelectionStart;
                    backupText = "";
                    compositionText = "";
    
                    this.SelectionStart = startPosition;
                    this.SelectionLength = 0;
                }
                return;
            case 0x010E://WM_IME_ENDCOMPOSITION
                return;
            case 0x010F://WM_IME_COMPOSITION
                {
                    isImmProcessing = true;
                    var gcs = (GCS)m.LParam.ToInt32();
    
                    //入力前の状態に戻す
                    this.SelectionStart = startPosition;
                    this.SelectionLength = compositionText.Length;
                    this.SelectedText = backupText;
                    this.SelectionStart = startPosition;
                    this.SelectionLength = backupText.Length;
                    this.SelectionColor = Color.Black;
                    this.SelectionLength = 0;
    
                    backupText = string.Empty;
                    this.SelectionStart = startPosition;
    
                    if ((gcs & GCS.GCS_RESULTSTR) == GCS.GCS_RESULTSTR || (gcs & GCS.GCS_RESULTCLAUSE) == GCS.GCS_RESULTCLAUSE)
                    {//変換終了か部分確定した
    
                            
                        //確定文字列を取得して挿入
                        var immdata = GetIMEString(true);
                        compositionText = immdata.Composition;
    
                        this.SelectionStart = startPosition;
                        this.SelectionLength = compositionText.Length;
                        this.SelectedText = compositionText;
                        this.SelectionStart = startPosition;
                        this.SelectionLength = compositionText.Length;
                        this.SelectionColor = Color.Black;
    
                        startPosition += compositionText.Length;
                        this.SelectionStart = startPosition;
                        this.SelectionLength = 0;
                    }
    
                    if ((gcs & GCS.GCS_COMPSTR) == GCS.GCS_COMPSTR)
                    {//変換中文字列がある
    
                        //未確定文字列を取得
                        var immdata = GetIMEString(false);
                        compositionText = immdata.Composition;
    
                        //未確定文字列が上書きしてしまう文字列を一時退避
                        this.SelectionStart = startPosition;
                        this.SelectionLength = compositionText.Length;
                        backupText = this.SelectedText;
    
                        //未確定文字を上書き
                        this.SelectedText = compositionText;
    
                        //変換状態によって文字の色を変更
                        this.SelectionStart= startPosition;
                        this.SelectionLength= 1;
                        foreach(ATTR attr in immdata.Attribute)
                        {
                            switch (attr)
                            {
                            case ATTR.ATTR_INPUT:
                                this.SelectionColor = Color.Purple ;
                                break;
                            case ATTR.ATTR_TARGET_CONVERTED:
                                this.SelectionColor = Color.Red;
                                break;
                            case ATTR.ATTR_CONVERTED:
                                this.SelectionColor = Color.Blue;
                                break;
                            case ATTR.ATTR_TARGET_NOTCONVERTED:
                                break;
                            case ATTR.ATTR_INPUT_ERROR:
                                break;
                            case ATTR.ATTR_FIXEDCONVERTED:
                                break;
                            default:
                                break;
                            }
                            this.SelectionStart = this.SelectionStart+1;
                            this.SelectionLength = 1;
                        }
    
                        this.SelectionStart = startPosition +immdata.CursorPosition ;
                        this.SelectionLength = 0;
                    }
                    isImmProcessing = false;
                    Invalidate();
                }
                return;
            default:
                break;
            }
            base.WndProc(ref m);
        }
    }
    実際にはフォントの状態を文字単位でバックアップしないといけないでしょう
    #画像があったりするとSelectedRtfに対して保持しないといけないですね

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2015年7月20日 11:05
    2015年7月19日 15:34