none
TextBoxで入力にフィルターをしたい。 RRS feed

  • 質問

  • Win32のように、WM_CHARで入力可能な文字をフィルターしたい。

    と言うのを、WPFのTextBoxで行いたいのですがうまく行きません。

    FrameworkPropertyMetadataのCoerceValueCallbackを使用してみたりしたのですが思うようにはなりませんでした。TextCompositionの動き等も良く解らず途方にくれております。

    2006年8月21日 1:31

すべての返信

  • KeyPressイベントで処理するのは駄目なのでしょうか?
    そういう事ではない?
    2006年8月21日 14:01
  • PreviewTextInput イベントPreviewKeyDown イベントではなくて、 直接 WM_CHAR メッセージを拾いたい?
    となると、 HwndHost 経由で Windows の世界に繋げることになるのでしょうが、 難しそうですね。

    ひょっとすると、 ↓このへんが役に立つかもしれません。
    MSDN Library: WinFX Development: Interoperability Input Architecture
    Nick on Windows Presentation Foundation (Avalon): IKeyboardInputSink (IKIS)

    2006年8月22日 5:03
  • WM_CHAR自体を拾いたいわけではなくて、例えば、数値しか入力出来ないTextBoxを作成したい。
    とした時、Win32だと、WM_CHARをフィルタリングして、数値のみデフォルト処理に渡すようにすると思います。
    こういった事をWPFのTextBoxで行う方法が解らないのです。

    ペースト(CTRL+V)等は、Paste コマンドをCommandBindingでトラップすれば何とかなります。
    ですが、Imeからの入力や、WPFのTextBoxだと、入力済み文字列を選択してスペースを押す事で再変換等もされます。
    こういったものの抑制等も含めて、特定の文字郡しか入力する事の出来ないTextBoxを作成したいのです。
    (音声入力も懸念事項の1つではありますが・・・)

    2006年8月22日 6:40
  •  FC-Shiro さんからの引用
    WM_CHAR自体を拾いたいわけではなくて、例えば、数値しか入力出来ないTextBoxを作成したい。
    とした時、Win32だと、WM_CHARをフィルタリングして、数値のみデフォルト処理に渡すようにすると思います。

    WPF では、 ウィンドウハンドルを持ってるのは最上位だけです。 そのメッセージループも、 簡単には触れないようになってしまっています。 (将来的には、 ウィンドウハンドルは全く無くなってしまうのではないか、 と想像しています。)
    また、 各コントロールにはウィンドウメッセージは飛んできません。 かわりに、 Routed Event が使われます。

     FC-Shiro さんからの引用
    ですが、Imeからの入力や、WPFのTextBoxだと、入力済み文字列を選択してスペースを押す事で再変換等もされます。
    こういったものの抑制等も含めて、特定の文字郡しか入力する事の出来ないTextBoxを作成したいのです。
    (音声入力も懸念事項の1つではありますが・・・)

    ほかにも、 手書き入力なんかも考慮しないといけないかもしれないですね。

    発想を変えて、 テキストボックスの文字 (Text) が変更された瞬間を狙うのはどうでしょう?
    次のように OnTextChanged イベントハンドラをオーバーライドしてしまっても、 目的は達成できるのではないかと思うのです。


    using System;
    using System.Text.RegularExpressions;

    namespace ExpressionTest21
    {
        public class TextBoxEx : System.Windows.Controls.TextBox
        {
            private bool _isProcessing = false;

            private void EraseNonNumeric()
            {
                this._isProcessing = true;

                String newText = Regex.Replace(this.Text, "[^0-9]", "", RegexOptions.Compiled);
                if (!newText.Equals(this.Text))
                {
                    int currentCursorPosition = this.SelectionStart;
                    int lengthDiff = this.Text.Length - newText.Length;
                    int newCursorPosition = currentCursorPosition - lengthDiff;

                    this.Text = newText;
                    this.SelectionStart = newCursorPosition;
                }

                this._isProcessing = false;
            }

            protected override void OnTextChanged(System.Windows.Controls.TextChangedEventArgs e)
            {
                if (this._isProcessing)
                {
                    return;
                }

                this.EraseNonNumeric();
                base.OnTextChanged(e);
            }
        }
    }

     

    2006年8月23日 0:37
  • ありがとうございます。

    この方法も試したのですが、漢字変換を行うと、変換中に数値以外が削除されてしまいます。
    (フィルターが数値のみ許可であれば漢字変換をONに出来ないようにと考えますが、そうではない場合には問題でしょう。)
    また、文字列を範囲選択してスペースを押す事で、漢字変換を行ってしまうと言う性質もあり
    既に入っている数値を範囲選択してスペースを2回叩くと範囲選択されていた数値が消える・・・と言う現象も出ます。

    なんで漢字変換中の文字列がTextに反映されるの? との疑問もありますが、現状がそうなっているので・・・。
    そういう仕組みで動くと言う事は、(現在は実装されていない)AutoCompleteも、Textプロパティを変更すると
    予想出来るのでもうちょっと違う方法を探さないとなぁ。と思う次第です。

    2006年8月23日 1:58
  • ようやく、 私なりに問題が把握できた気がします。

     FC-Shiro さんからの引用
    なんで漢字変換中の文字列がTextに反映されるの? との疑問もありますが、現状がそうなっているので・・・。

    変換候補を切り替えるたびに、 (変換を確定させていないのに、) Text プロパティが変わっちゃうわけですね。 困ったことに、 変えられた Text プロパティを、 OnTextChanged() あたりで元に戻すと、 IME の変換も終わってしまう、 と。

    この新しい仕様のおかげで、 TextBox の MaxLength プロパティも使いにくくなってしまいました。
    例えば、 MaxLength=3 になっているときに、 "123" とキー入力して、 変換し、 候補の "百二十三" を選んだら、 変換が終わってしまいます。

    それで…。 PreviewKeyDown イベントハンドラの引数に渡ってくる System.Windows.Input.KeyEventArgs の Key プロパティと ImeProcessedKey プロパティを見ることで、 IME で変換操作をしている時のキー入力を拾うことはできます。
    変換操作中は、 文字種や最大長のチェックをしないでおいて、 ユーザが変換を確定させた時点でチェックすればよさそうです。

    が。 IME が変換を確定させた、 ということはどうやったら分かるのでしょう?
    Enter キー以外にも確定させるキーはありますし、 マウスクリックでも確定できます。 けっきょく、 IME 自体から 「確定したよ」 と教えてもらえない限りは、 解決するのは難しいと思います。
    # 少し MSDN をあさってみたのですが、 見つかりませんでした  

    2006年8月23日 9:15
  •  biac さんからの引用

    が。 IME が変換を確定させた、 ということはどうやったら分かるのでしょう?
    Enter キー以外にも確定させるキーはありますし、 マウスクリックでも確定できます。 けっきょく、 IME 自体から 「確定したよ」 と教えてもらえない限りは、 解決するのは難しいと思います。
    # 少し MSDN をあさってみたのですが、 見つかりませんでした  

    今すぐに試す環境がなくてポインタの提示のみになりますが、WPF は TSF (Text Services Framework / CICERO) を使っていませんでしたっけ?

    TSF 環境では、従来の IME とはデータ管理が大幅に異なるので、今回提示されているような現象が起きても不思議ではないように思います。

    TSF 環境では、アプリケーション側がテキストストアを持ち、テキストサービスがそれをトランザクショナルに更新するというアーキテクチャを採っています。従って、コンポジション中の中間状態の管理は、アプリケーション側の責任で行われます。
    これは、状態管理のほとんどの部分を IME に任せていた従来の形態とは大きく異なります。
    ボトムアップに追いかける際のご参考までに。

    Text Services Framework

    2006年8月23日 18:42
  •  biac さんからの引用

    この新しい仕様のおかげで、 TextBox の MaxLength プロパティも使いにくくなってしまいました。
    例えば、 MaxLength=3 になっているときに、 "123" とキー入力して、 変換し、 候補の "百二十三" を選んだら、 変換が終わってしまいます。


    TextBox の MaxLength プロパティについては試してなかったので、ちょっと試してみました。
    すると、MaxLength="5"とした時に、漢字入力で「12345」と入力し、変換して候補を変えていくと・・・・

    System.Runtime.InteropServices.COMException がネイティブまたはマネージ境界を越えました
      Message="Document cannot accept composition content."
      Source="PresentationFramework"
      ErrorCode=-2147467259
      StackTrace:
           場所 System.Windows.Documents.TextStore.SetText(SetTextFlags flags, Int32 startIndex, Int32 endIndex, Char[] text, Int32 cch, TS_TEXTCHANGE& change)

     こんな例外で落ちてしまいました・・・。
    なんか、バグっぽいですねぇ。

     NyaRuRu さんからの引用

    今すぐに試す環境がなくてポインタの提示のみになりますが、WPF は TSF (Text Services Framework / CICERO) を使っていませんでしたっけ?

    情報ありがとうございます。提示のリンクも見てみたのですがText Services Framework のみ書かれていて、WPFとの関連についてはちょっと解りませんでした。もっと調べてみないといけないですね。

    2006年8月24日 7:35
  •  NyaRuRu さんからの引用
    TSF 環境では、アプリケーション側がテキストストアを持ち、テキストサービスがそれをトランザクショナルに更新するというアーキテクチャを採っています。従って、コンポジション中の中間状態の管理は、アプリケーション側の責任で行われます。

    ありがとうございます。 教えていただいた url からたどって、 すこし TSF を齧りました。 (よーやく )

    で、 IME の状態は コンポジション で知ることが出来そうだと分かったので、 WPF のほうで TextComposition を調べてみて、 TextCompositionManager のイベントに行き当たりました。 私が知りたかったのは、 たぶんコレです。 ( FC-Shiro さんの求めているものとは違うと思いますが… )

    IME をオンにしてキー入力を始める:
    (Preview)TextInputStart イベントが発生。 キー入力前に選択していた文字列があったときは、 e.CompositionText に入ってくる。

    変換候補の切り替え、変換中の文字列の変更:
    (Preview)TextInputUpdate イベントが発生。 e.CompositionText に、変換中の文字列が入ってくる。
    ※ この時点では、 TextBox.Text は以前のまま。 このイベントに引き続いて、 TextBox.OnTextChanged イベントが呼びだされる。

    変換確定:
    (Preview)TextInput イベントが発生。 e.Text に、 確定した文字列が入ってくる。
    ※ すでに TextInputUpdate の後で、 TextBox.Text は変わってしまっている。  この時点で e.Handled = false; にしても、 テキストボックスの表示は元に戻らない。


    確認するために書いたコードを載せておきます。


        public class TextBoxEx : System.Windows.Controls.TextBox
        {
            // TextCompositionEventHandlers
            public void OnPreviewTextInputStart(object sender, System.Windows.Input.TextCompositionEventArgs e)
            {
                // [IME on] 入力開始時に呼ばれる
                // [IME off] テキスト入力時に呼ばれる
                Trace.WriteLine(String.Format("(TextComposition)OnPreviewTextInputStart: Text is '{0}', CompositionText is '{1}'", e.Text, e.TextComposition.CompositionText));
                // Text is '', CompositionText is '' --- どちらも空
                //     (変換開始前に選択していた時は、CompositionText に選択していた文字列)
                //※ ここで this.CaretIndex を見れば、再変換の対象になった文字列がわかる。
            }
            public void OnPreviewTextInputUpdate(object sender, System.Windows.Input.TextCompositionEventArgs e)
            {
                // [IME on] 変換候補切り替え時と、変換文字列の変更時に呼ばれる。 (なぜか 3回ずつ?)
                // [IME off] 呼ばれない
                Trace.WriteLine(String.Format("(TextComposition)OnPreviewTextInputUpdate: Text is '{0}', CompositionText is '{1}'", e.Text, e.TextComposition.CompositionText));
                // Text is '', CompositionText is 'A' --- CompositionText に、変換中の文字列
                //※ この後、TextBox.OnTextChanged イベントが呼びだされる。
            }
            public void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
            {
                // [IME on] 変換確定時に呼ばれる。
                // [IME off] テキスト入力時に呼ばれる。 (OnPreviewTextInputStart に連続して。)
                Trace.WriteLine(String.Format("(TextComposition)OnPreviewTextInput: Text is '{0}', CompositionText is '{1}'", e.Text, e.TextComposition.CompositionText));
                // Text is 'A', CompositionText is '' --- Text に、変換した文字列 (TextBox.Text 全体ではない) (CompositionText は空に戻る)
                //※ IME で入力しているときは、すでに TextBox.Text は変わってしまっているため、
                //   このあとで TextBox.OnTextChanged イベントは発生しない。
                //   ( IME を使っていないときは、このあとで OnTextChanged イベントが発生する。)
                //※ これと、次の OnTextInput イベントは、TextBox で同じものが公開されている。
            }
            public void OnTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
            {
                Trace.WriteLine(String.Format("(TextComposition)OnTextInput: Text is '{0}', CompositionText is '{1}'", e.Text, e.TextComposition.CompositionText));
                // …ここには来ない。 bug? or by design?
            }
            //※ cut, paste したときは、上のいずれのイベントも発生せずに、
            //   いきなり TextBox.OnTextChanged イベントが発生する。 


            public TextBoxEx()
            {
                System.Windows.Input.TextCompositionManager.AddPreviewTextInputStartHandler(this, OnPreviewTextInputStart);
                System.Windows.Input.TextCompositionManager.AddPreviewTextInputUpdateHandler(this, OnPreviewTextInputUpdate);
                System.Windows.Input.TextCompositionManager.AddPreviewTextInputHandler(this, OnPreviewTextInput);
                System.Windows.Input.TextCompositionManager.AddTextInputHandler(this, OnTextInput);
            }
        }

     

    2006年8月24日 9:04
  • 文字化けが発生する。なぜ ?

    今までのを統合して、こんな風にしてみました。

        public class TextBoxEx : System.Windows.Controls.TextBox
        {
            private bool _isStartingTextComposition = false;
            private bool _isProcessing = false;

            public delegate void CompositionCompleteDelegate();

            private void OnPreviewTextInputStart(object sender, System.Windows.Input.TextCompositionEventArgs e)
            {
                _isStartingTextComposition = true;
            }
            private void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
            {
                _isStartingTextComposition = false;

                // ここでthis.EraseNonNumericを呼ぶと、あとで落ちる。
                // this.EraseNonNumeric();

                // しょうがないので、Dispatcher.BeginInvokeで対応?
                // 本当は、この後でOnTextChangedが呼ばれて、その中で処理と言うのが一番良い筈!

                Dispatcher.BeginInvoke(
                            System.Windows.Threading.DispatcherPriority.Normal,
                            new CompositionCompleteDelegate(OnCompositionComplete));
            }

            private void OnCompositionComplete()
            {
                this.EraseNonNumeric();
            }

            private void EraseNonNumeric()
            {
                this._isProcessing = true;

                String newText = Regex.Replace(this.Text, "[^0-9]", "", RegexOptions.Compiled);
                if (!newText.Equals(this.Text))
                {
                    int currentCursorPosition = this.SelectionStart;
                    int lengthDiff = this.Text.Length - newText.Length;
                    int newCursorPosition = currentCursorPosition - lengthDiff;

                    this.Text = newText;
                    this.SelectionStart = newCursorPosition;
                }

                this._isProcessing = false;
            }

            protected override void OnTextChanged(System.Windows.Controls.TextChangedEventArgs e)
            {
                if (this._isProcessing) {
                    return;
                }
                if (!this._isStartingTextComposition) {
                    this.EraseNonNumeric();
                }
                base.OnTextChanged(e);
            }

            public TextBoxEx()
            {
                System.Windows.Input.TextCompositionManager.AddPreviewTextInputStartHandler(this, OnPreviewTextInputStart);
                System.Windows.Input.TextCompositionManager.AddPreviewTextInputHandler(this, OnPreviewTextInput);
            }
        }

    こんな感じでしょうか ?

    もっと良い方法などありましたらご指摘ください。

    2006年8月25日 7:57
  •  FC-Shiro さんからの引用
                // ここでthis.EraseNonNumericを呼ぶと、あとで落ちる。
                // this.EraseNonNumeric();

                // しょうがないので、Dispatcher.BeginInvokeで対応?
                // 本当は、この後でOnTextChangedが呼ばれて、その中で処理と言うのが一番良い筈!


    TextInput イベント、 トンネル時にはまだ IME のドロップダウンリストが出たまま、 つまり、 まだ IME の処理が終わっていないので、 おかしくなるのではないでしょうか。
    バブル時に処理すればいいのではないかと思ってますが、 なんせ、 現状ではイベントが発生しない (たぶん、 未実装) なので…

    なお、 TextInput イベントが発生する前、 TextInputUpdate イベントの直後に Text プロパティは書き換えられて、 TextChanged イベントはすでに起きてしまっています。
    なので、 IME から入力しているときは、 TextInput イベントの後では TextChanged イベントは発生しないです。
    #  PreviewTextInput イベントで Handled=true にしたときの挙動が、 IME の on/off で異なるので、 しばらく悩んでいました。

    それと、 MaxLength の扱いですが。
    あらかじめ、 セットされた MaxLength の値を退避しておいて ( たとえば、 次のように OnPropertyChanged イベントでやるとかして)、 TextInputStart イベントで MaxLength=0 に変えて、 TextInput イベントで元に戻す …というような手段で回避するしかないのかなぁ、 と思っています。
    # MaxLength プロパティがオーバーライドできれば、 base.MaxLength は 0 のままにしておけるので、 キレイにまとまるんでしょうけど…


            private int _maxLength = 0;

            protected override void OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs e)
            {
                if ("MaxLength".Equals(e.Property.Name))
                {
                    this._maxLength = base.MaxLength;
                }
                base.OnPropertyChanged(e);
            }

     

     

     FC-Shiro さんからの引用
    文字化けが発生する。なぜ ?

    なんか、 長いレスを再編集すると化けるらしいですね  → 「再編集すると日本語がすべて?になるようです。

    2006年8月29日 0:56
  •  biac さんからの引用
    TextInput イベント、 トンネル時にはまだ IME のドロップダウンリストが出たまま、 つまり、 まだ IME の処理が終わっていないので、 おかしくなるのではないでしょうか。
    バブル時に処理すればいいのではないかと思ってますが、 なんせ、 現状ではイベントが発生しない (たぶん、 未実装) なので…

    Windows Vista pre-RC1 で試したところ…
    ・ TextInput バブルイベント等は、 実装されていた。
    ・ TextInput バブルイベント時にも、 まだ IME のドロップダウンリストは出たままだった
    2006年9月6日 9:12
  •  biac さんからの引用
    Windows Vista pre-RC1 で試したところ…
    ・ TextInput バブルイベント等は、 実装されていた。
    ・ TextInput バブルイベント時にも、 まだ IME のドロップダウンリストは出たままだった

    ImeはVista標準の奴でしょうか ?

    Office2007Betaで入るImeでちょこっと試したら、ドロップダウンリストがずっと出っ放しになったり、色々変な現象が起きたので。

    この辺の再検証まで手が回らない~

    2006年9月26日 9:55
  •  FC-Shiro さんからの引用
    ImeはVista標準の奴でしょうか ?

    しまった。 すみません、 確認してませんでした。

    たしかに Office 2007 beta を入れていたので、 そっちの IME を使ってた可能性があります。
    その後、 pre-RC1 から RC1 に上げてしまったので、 もはやどっちだったのか確認できません。

    で、 今、 RC1 になった環境を見てみました。

    ・標準
    IME: バージョン情報は "Microsoft(R) IME (10.0.5600.16384)" と表示されます。
    TextInput バブルイベント: やっぱり、 IME のドロップダウンリストは出たままです。

    ・もうひとつの IME
    IME: バージョン情報は "Microsoft(R) IME 2007 (Beta) (12.0.4017.1003)" と表示されます。
        こちらが、 Office 2007 beta のものでしょう。
    TextInput バブルイベント: やっぱり、 IME のドロップダウンリストは出たままです。

    …って、 しまった。 Technical Refresh を当ててないや

    2006年9月27日 1:42
  •  biac さんからの引用
    …って、 しまった。 Technical Refresh を当ててないや

    Office 2007 beta2 Technical Refresh を当てました。

    ・もうひとつの IME
    IME: バージョン情報は "Microsoft(R) Office IME 2007 (Beta) (12.0.4407.1001)" と表示されます。
        これが、 Office 2007 beta TR のものですね。 Build Time も Aug 11 となっています。
    TextInput バブルイベント: やっぱり、 IME のドロップダウンリストは出たままです。

    2006年9月27日 2:48