テキスト入力を制限する添付プロパティ

WPFは、市場に出ている新しい技術とはほど遠いですが、私にとっては比較的新しいものです。 そして、新しいことを学ぶときによくあることですが、いくつかの典型的な問題を解決するために、四角い車輪と合金の車輪を備えた自転車の発明に対する要望/ニーズがあります。



そのようなタスクの1つは、特定のデータのユーザー入力を制限することです。 たとえば、あるテキストフィールドには整数値のみを入力し、別のフィールドには特定の形式の日付を入力し、3番目のフィールドには浮動小数点数のみを入力します。 もちろん、このような値の最終的な検証はビューモデルでも行われますが、入力に関するこのような制限により、ユーザーインターフェイスはより使いやすくなります。



Windows Formsでは、このタスクは非常に簡単に解決されました。また、DevExpressの同じTextBoxが正規表現を使用して入力を制限する組み込み機能を備えている場合、すべてが一般的に単純でした。 WPFでこの問題を解決する例はかなりありますが、そのほとんどは、 TextBoxクラス継承を使用するか、必要な制限付きの添付プロパティを追加する2つのオプションのいずれかになります





私の推論にあまり興味がなく、すぐにコードサンプルが必要な場合は 、GitHubからWpfEx プロジェクト全体をダウンロードするか TextBoxBehavior.cs および TextBoxDoubleValidator.csに 含まれるメイン実装をダウンロードできます



さあ、始めましょうか?




継承にはかなり厳しい制限が導入されるため、このメカニズムでは特定のタイプのコントロールにこれらのプロパティの適用を制限できるため、この場合は添付プロパティを使用することを個人的に好みます(この添付IsDoubleプロパティ TextBlockに適用したくない意味がありません)。

さらに、ユーザー入力を制限する場合、整数部分と小数部分(「。」(ピリオド)または「、」(コンマ)など)の特定の区切り記号、および記号「+」と「-」を使用できないことに注意してください。すべてはユーザーの地域設定に依存するためです。

データ入力を制限する可能性を実現するには、ユーザーのデータ入力イベントを手動でインターセプトし、それを分析して、これらの変更が適切でない場合はこれらの変更を破棄する必要があります。 XXXChangedイベントXXXChangingイベントのペアを使用するWindows Formsとは異なり、WPFはメインイベントが発生しないように処理できる同じ目的でイベントのプレビューバージョンを使用します。 (典型的な例は、特定のキーまたはそれらの組み合わせを禁止する、マウスまたはキーボードからのイベントの処理です)。



また、 TextBoxクラスとTextChangedイベントにPreviewTextChangedが含まれていて、入力テキストが正しくないと見なされた場合、ユーザーによるデータ入力の処理と「中断」が可能であればすべて問題ありません。 そして、それは存在しないので、誰もが彼自身のリスペットを発明しなければなりません。



問題解決




この問題の解決策は、添付されたIsDoublePropertyプロパティを含むTextBoxBehaviorクラスを作成することです。その後、ユーザーはこのテキストフィールドに+、-、以外の文字を入力できなくなります。 (整数部分と小数部分の区切り)、および数字(ハードコーディングされた値ではなく、現在のストリームの設定を使用する必要があることを忘れないでください)。



public class TextBoxBehavior { // Attached   ,     //     public static readonly DependencyProperty IsDoubleProperty = DependencyProperty.RegisterAttached( "IsDouble", typeof (bool), typeof (TextBoxBehavior), new FrameworkPropertyMetadata(false, OnIsDoubleChanged)); //      IsDouble     // UI ,  TextBox    [AttachedPropertyBrowsableForType(typeof (TextBox))] public static bool GetIsDouble(DependencyObject element) {} public static void SetIsDouble(DependencyObject element, bool value) {} // ,   TextBoxBehavior.IsDouble="True"  XAML- private static void OnIsDoubleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { //   } }
      
      







PreviewTextInputハンドラーの実装の主な複雑さ(およびクリップボードからテキストを貼り付けるイベント)は、テキストの合計値ではなく、新しく導入された部分のみがイベント引数で送信されることです。 したがって、要約されたテキストは、TextBoxでテキストを選択する可能性、テキスト内の現在のカーソル位置、および場合によっては挿入ボタンの状態(分析しません)を考慮して、手動で生成する必要があります。



 // ,   TextBoxBehavior.IsDouble="True"  XAML- private static void OnIsDoubleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { //     attached    // TextBox   ,    -  var textBox = (TextBox) d; //       : // 1.     // 2.      textBox.PreviewTextInput += PreviewTextInputForDouble; DataObject.AddPastingHandler(textBox, OnPasteForDouble); }
      
      







クラスTextBoxDoubleValidator




2番目の重要なポイントは、新しく入力されたテキストの検証ロジックの実装です。このロジックの責任は、個別のTextBoxDoubleValidatorクラスのIsValidメソッドに割り当てられます。



このクラスのIsValidメソッドの動作を理解する最も簡単な方法は、すべてのコーナーケースをカバーするユニットテストを記述することです(これは、パラメーター化されたユニットテストがひどい力でルールを設定する場合の1つにすぎません)





これは、単体テストが特定の機能の実装の正確性を検証する単なるテストではない場合に当てはまります。 これは、説明責任を説明するときにケントベックが繰り返し語ったことです。 このテストを読んだ後、検証メソッドの開発者が何を考えていたかを理解し、知識を「再利用」して、推論、したがっておそらく実装コードのエラーを見つけることができます。 これは単なるテストスイートではなく、このメソッドの仕様の重要な部分です。



 private static void PreviewTextInputForDouble(object sender, TextCompositionEventArgs e) { // e.Text    ,     //  TextBox-   var textBox = (TextBox)sender; string fullText; //  TextBox   ,     e.Text if (textBox.SelectionLength > 0) { fullText = textBox.Text.Replace(textBox.SelectedText, e.Text); } else { //          fullText = textBox.Text.Insert(textBox.CaretIndex, e.Text); } //     bool isTextValid = TextBoxDoubleValidator.IsValid(fullText); //    TextChanged    e.Handled = !isTextValid; }
      
      







textパラメーターが有効な場合、テストメソッドはtrueを返します。これは、対応するテキストをIsDoubleプロパティがアタッチされたTextBoxに駆動できることを意味します 。 いくつかの点に注意してください:(1)目的のロケールを設定するSetCulture属性の使用、および(2) Double型の有効な値ではない「-。」などの入力値。



ロシア語のロケールでは記号「、」(コンマ)が区切り文字として使用され、アメリカの「-」で使用されるため、テストが他の個人設定を持つ開発者に該当しないように、ロケールの明示的なインストールが必要です。 (期間)。 ユーザーが文字列「-.1」を入力する場合はエントリを完了する必要があるため、「-。」などの奇妙なテキストは正しいです。これはDoubleの正しい値です。 (興味深いことに、StackOverflowでは、 Double.TryParseを使用してこの問題を解決することをお勧めしますが、これは明らかに場合によっては機能しません)。





IsValidメソッドの実装の詳細で記事を煩雑にしたくありません。各メソッドのローカルのDoubleSeparatorを取得およびキャッシュできるThreadLocal <T>のこのメソッドの本体での使用に注意したいだけです。 TextBoxDoubleValidator.IsValidメソッドの完全な実装はここにあります; ThreadLocal <T>の詳細は、Joe Albahariの記事「 Working with Streams」にあります。 パート3



代替ソリューション




PreviewTextInputイベントをキャプチャし、クリップボードからテキストを貼り付けることに加えて、他のソリューションがあります。 そのため、たとえば、数値以外のすべてのキーをフィルタリングしてPreviewKeyDownイベントをインターセプトすることにより、同じ問題を解決しようと試みました。 ただし、この解決策はさらに複雑です。TextBoxの「概要」状態に煩わされる必要があるためです。理論的には、整数部分と小数部分の区切り文字は1文字だけでなく、文字列全体( NumberFormatInfo.NumberDecimalSeparatorcharではなくstringを返します)です。

KeyDownイベントにはTextBox.Textの以前の状態を保存する別のオプションがあり、 TextChangedイベントには新しい値が合わない場合に古い値を返す別のオプションがあります。 しかし、このソリューションは不自然に見えるため、添付プロパティを使用して実装するのはそれほど簡単ではありません。



おわりに




WPFの便利で非常に典型的な機能の欠如について同僚と最後に話し合ったとき、これには説明があり、これには肯定的な側面があるという結論に達しました。 この説明は、穴のあいた抽象化法則から逃れることができないという事実に要約され、 WPFは「抽象化」非常に複雑であり、ふるいのように流れます。 有用な側面は、いくつかの有用な機能がないために時々考えるようになることです(!)そして、私たちはプログラマーであり、コピーアンドペーストの職人ではないことを忘れないでください。



与えられたクラスのクラスの完全な実装、それらの使用例、単体テストはgithubで見つけることができることを思い出させてください。




All Articles