WPF Prism episode: 9 ~ ReactiveProperty の Validation は DataAnnotation じゃないと思った? ~

← 前回記事【WPF Prism episode: 8 ~ ReactiveProperty がバインドできないのはどう考えても Navigation が悪い! ~】

前回は TreeView へのコンテキストメニューの追加と、Prism.INavigationAware インタフェースの IsNavigationTarget メソッドと OnNavigatedFrom メソッドを紹介したので、今回は WPF の Validation を ReactiveProperty へ適用する方法を紹介したいと思います。

尚、この記事は Visual Studio 2017 Community Edition で .NET Framework 4.7.2 以上 と C# + Prism 7.1 + ReactiveProperty を使用して 、WPF アプリケーションを MVVM パターンで作成するのが目的で、C# での基本的なコーディング知識を持っている人が対象です。

WPF の Validation

今までのエントリではあえて触れていませんでしたが、アプリには必須(業務系アプリ等では最重要)とも言える Validation について紹介します。
WPF の Validation には歴史的な経緯から何通りかの方法があって、SourceChord の 『WPFでの入力値検証・まとめ』 が、日本語で書かれた情報としては最もまとまっていると思うので、一通り目を通すと概要が把握できると思います。

ですが、このサンプルアプリで使用する Validation は、DataAnnotations のみなので、SourceChord の『WPFでの入力値検証・その3 ~DataAnnotationsを使う~』だけでも構いません。

DataAnnotation は元々 ASP.NET で使用するために用意された機能のようですが、WPF でも ASP.NET と同じように使用できます。
System.ComponentModel.DataAnnotations 名前空間】配下にはアノテーションに使用するためのクラスが多数用意されていますが、ReactiveProperty に DataAnnotation を適用する場合であれば以下の 2 クラスでほぼカバーできると管理人は個人的に考えています。

  • RegularExpression 属性
  • Required 属性

中でも特に有用なのが【RegularExpression 属性】で、項目の単体チェックであれば、ほぼこの属性のみで事足りると言えるでしょう。
正規表現が苦手な人や面倒な人には、Regular Expression Library のようなサイトもあるので、似たパターンを探して少し手を入れれば、整数・小数桁数チェックまで含めた数値チェック等はすぐに見つかるはずです。下に出て来るサンプルソースに書いた正規表現もこのサイト で見つけたものです。

DataAnnotation を使用した Validation

DataAnnotation とは、以下のように Validation 対象プロパティへ設定する属性を指し、適用するにはまず【System.ComponentModel.DataAnnotations】名前空間を using する必要がありますが、追加時に IntelliSense へ候補が表示されない場合は参照をプロジェクトへ追加します。
管理人の場合、自分では参照を追加していませんが using 記述時、候補に表示されたので ReactiveProperty 等が裏で参照しているからなのかもしれません。

サンプルアプリの身体測定データ編集 View を例に DataAnnotation を適用したコードが以下になります。
測定日は後回しにして、まずは数値項目のみに DataAnnotation を追加します。

Height・Weight プロパティに RegularExpression 属性を追加して、整数部 3 桁・小数部 2 桁の正の数値のみが有効になる正規表現パターンを指定しています。
併せて ReactiveProperty の初期化時に『SetValidateAttribute メソッド』を追加して、ラムダ式へ対象のプロパティを渡すことで属性で設定した Validation が有効になります。(50、54 行目)

又、ToReactivePropertyAsSynchronized メソッドの省略可能第 3 パラメータ【ignoreValidationErrorValue】に true を設定して、Validation でエラーになった値が Model へ反映されるのを防いでいます

続いて XAML 側です。

現時点で XAML に変更が必要な箇所はありませんが、バインディングに『UpdateSourceTrigger=PropertyChanged』を追加して、リアルタイムに Validation が実行されるよう指定しています。
このプロパティを設定しない場合のデフォルト値は【LostFocus】で Validation が実行されるのは別のコントロールへフォーカスが移動した際に行われるので必須の設定ではありませんが、RegularExpression 属性を指定した場合は 1 文字毎リアルタイムで Validation を実行してくれるので UX を高めるのに効果があると思います。
(パフォーマンスは LostFocus と比べてもちろん悪くはなります)

ここまで記述して実行すると下の fig.1 のように不正データを入力するとエラーを示す赤枠で囲まれるようになります。

fig.1 Validation の例

ここまで DataAnnotation に設定するエラーメッセージは直接ソースコードにリテラル文字列を設定してきましたが、実際のアプリ作成時は文字列を直接記述するより SourceChord の『WPFでの入力値検証・その9 ~エラー表示のローカライズ~』 に書かれているように、多言語対応するプロジェクトとして作成し、エラーメッセージ部分をリソースに切り出す方が何かと取り回しやすいと思います。

このエントリでは多言語への対応方法等の紹介はしないので、必要な場合は上記 SourceChord のエントリを参考にしてください。

(2019/2/26 エントリの再構成時に上記の文が抜けていたため追加)

数値型プロパティを TextBox へバインド

Height プロパティのような数値型で定義されたプロパティをバインドした TextBox へ「aa」のような数値以外の文字を入力した場合、WPF 側が内部的に Parse するようで、VM まで値が渡ってきませんし、イミディエイトウィンドウにスタックトレースが出力されるだけで、例外も発生しません

fig.2 Validation 実行時のスタックトレース出力

そのため、上で紹介した ReactiveProperty の ToReactivePropertyAsSynchronized 拡張メソッドのパラメータへ【ignoreValidationErrorValue: true】を指定しても、しなくても Model へ数値以外がセットされる事は無いようですが、Model と双方向で接続していて Validation を設定したプロパティには【ignoreValidationErrorValue: true】を常に指定する方が良いと思います。

又、double や Decimal 等のような小数部の入力がある数値を TextBox とバインドしている場合、標準のままでは「168.5」のような値をダイレクトに入力できません。

詳しくは Qiita の『doubleとバインドしているTextBoxに小数点が入力できない!』 へ書かれているように、『UpdateSourceTrigger = PropertyChanged』が設定された TextBox で発生するようで、以下の 2 エントリで解決できるようです。

結果的にコンバーターを修正して対応しているようですが、管理人的には TextBox を継承した NumberText 等のカスタムコントロールを作成する方法を選択した方が良いと思っていますが、探すとフリーのコントロールがあったので試してみました。

Extended WPF Toolkit™

Xceed 製品の Community Edition で、元は CodePlex で配布していたようですが、CodePlex 終了後、現在は GitHub + NuGet で、49 個のカスタムコントロールを含んだパッケージ(2019/2/23 現在 Ver.3.5.0)として配布されています。

前章でも出てきた数値入力コントロールを初めとして、MaskedTextBox、RichTextBox 等も含まれていて、現時点(2019年2月現在)でもバグフィックス等は行われているようで、ちょっとしたアプリに組み込むのであれば十分だと思います。

但し、現時点では .NET Framework 4.0 までの対応で、.NET Framework 4.5 には未対応のようですし、.NET Core 3.0 への対応も GitHub では何も触れられていないので、将来での対応を期待して業務用システムへ採用するのは難しいかもしれませんが、個人的に使うなら気軽に使えそうなので使ってみることにします。

Extended WPF Toolkit は NuGet を『extended』で検索すると見つかります。

fig.3 Extended WPF Toolkit を NuGet で検索

検索結果の【Extended.WPF.Toolkit】を選択して、EditorViews プロジェクトへインストールします。
インストール終了後、『PhysicalEditor.xaml』を以下のように編集します。

まず、Extended WPF Toolkit を使用するために名前空間にエイリアスを設定します。(3 行目)
そして、WPF 標準の DatePicker を Extended WPF Toolkit の DateTimePickerに、TextBox を Extended WPF Toolkit の DoubleUpDown に置き換えます。

コントロールを入れ替える場合、XAML ならコントロール名を書き換えるだけでバインディングやマージン等のプロパティを修正する必要がほとんど無いのは本当に XAML の利点と言えます。
Windows Form でも Designer ファイルを手動で書き換えれば同じことができる気がしますが、やったことはありません。
管理人が Windows Form でコントロールを入れ替える場合、フォームデザイナから削除した後に新しいコントロールを追加してからプロパティ等を再設定していたので、それと比べると XAML でのコントロール入れ替えは格段に楽になっていると言えます。

Extended WPF Toolkit DateTimePicker コントロールに設定しているプロパティの簡易説明です

プロパティ名内容
Value元の DatePicker では SelectedDate プロパティでしたが、Value プロパティへ修正する必要があります
Watermark以下 fig.4、5 に表示されている「測定日を選択」等のような項目の説明を取得・設定します。
ShowButtonSpinner入力エリアの UpDown ボタンの表示を取得・設定します。

fig.4 UpDown ボタンあり

fig.5 UpDown ボタン無し

AutoCloseCalendartrue を設定するとドロップダウンカレンダーで日付を選択した後、カレンダーが自動で閉じられます。
false を設定した場合は、日付を選択した後もカレンダーは閉じられません。
Format入力する日付・時刻のフォーマットを DateTimeFormat 列挙型の内から 1 つ取得・設定します。
設定内容は英語のページですが、Extended WPF Toolkit: New DateTimeUpDown control – Brian Lagunas Blog 等が参考になると思います。
TextAlignment値の水平位置を取得・設定します。
TimePickerVisibility時刻入力用 Picker の表示・非表示状態を取得・設定します。

fig.6 TimePickerVisibility=”Visible”

fig.7 TimePickerVisibility=”Hidden”

fig.8 TimePickerVisibility=”Collapsed”

 

Extended WPF Toolkit DoubleUpDown コントロールに設定しているプロパティの簡易説明です。
Extended WPF Toolkit にはこの DoubleUpDown コントロールを始めとして IntegerUpDown、DecimalUpDown、ByteUpDown 等数値の型に合わせて数種類の NumericUpDown コントロールが含まれています。

プロパティ名内容
Value元の TextBox では Text プロパティでしたが、Value プロパティへ修正する必要があります
AutoSelectBehavior【OnFocus】に設定していると、フォーカスを受け取った際にテキストが全選択されます。
MouseWheelActiveOnFocustrue に設定すると、マウスホイールで値の Up・Down ができるようになります。
MouseWheelActiveTrigger上記 MouseWheelActiveOnFocus = true に設定した場合、マウスホイールを受け取るトリガーを指定できます。
「フォーカスがある場合」、「マウスカーソルがコントロール上にある場合」等が選択できます。
ShowButtonSpinner入力エリアの UpDown ボタンの表示を取得・設定します。

fig.9 UpDown ボタンあり

fig.10 UpDown ボタン無し

Maximum入力可能な最大値を取得・設定します。
Minimum入力可能な最小値を取得・設定します。
ParsingNumberStyle入力を許容する数値のスタイルを取得・設定します。
ここでは double 型の入力項目として使用するので『Float』を設定しています。
Float を設定してもアルファベット等は入力できますが、フォーカスが外れると無効な入力はクリアされます。
又、前章で紹介した Parse エラー等も発生しなくなります。

ここで紹介したプロパティはごく一部なので、上記以外のプロパティについては Extended WPF Toolkit の GitHub Wiki やオブジェクトブラウザ等で確認してください。

元々試しに使ってみようと思っていましたが、Prism や ReactiveProperty の検証に気を取られていて今まで存在をすっかり忘れていました。

とりあえず、このサンプルアプリで使用しているコントロールは全て Extended WPF Toolkit のコントロールに入れ替えることにします。
このコントロールを使えば、前章で紹介した「小数点の数値がダイレクトに入力できない」とか、「フォーカスを受け取ったらテキストを全選択しない」と言うような不満も全て解消されるので、入れ替える手間をかけても価値はあると思います。

IME の制御

Extended WPF Toolkit を軽く触ってみて使えそうだと思いましたが、やはり海外製品と言うべきか、IME を無効にする設定は無視されてしまうようで、数値入力専用コントロールのはずなのに、DoubleUpDown コントロール等で IME を ON にできてしまいます。
通常 WPF で IME を無効にする場合は、以下のように InputMethod 添付プロパティを使用します。

Extended WPF Toolkit の数値入力系コントロールでは、上記のように InputMethod 添付プロパティを追加しても IME の On/Off が可能なままです。
(同梱されている AutoSelectTextBox には InputMethod 添付プロパティが反映されます)
但し、全角文字等が入力できるからと言ってもフォーカスが外れると無効な文字は削除されるので、大きな問題にはならないと思います。

Extended WPF Toolkit DateTimePicker と Nullable<DateTime>

このエントリの公開準備をしていて気が付いたんですが、Extended WPF Toolkit の DateTimePicker.Value プロパティへ Nullable<DateTime> をバインドするとおかしな動きをします。

WPF 標準の DatePicker の場合、以下のような動作をします。

fig.11 WPF 標準の DatePicker

少し分かりにくいかもしれませんが、WPF 標準の DatePicker では日付をドロップダウンカレンダーから選択した後、日付をクリアして別のコントロールへフォーカスを移すとエラーメッセージ「必須入力です。」が表示されます。

又、エラーメッセージ「必須入力です。」が表示されている状態で、再度日付を選択(選択する日付は最初にドロップダウンカレンダーから選択した日付)してもとちゃんと選択された値がセットされています。

これを Extended WPF Toolkit の DateTimePicker を配置して同じ操作をすると、以下のように WPF 標準の DatePicker とは違った動作になります。

fig.12 Extended WPF Toolkit の DateTimePicker

Extended WPF Toolkit の DateTimePicker の場合、ドロップダウンカレンダーで日付を選択した後、日付をクリアして別のコントロールへフォーカスを移してもエラーが表示されません

又、ドロップダウンカレンダーで日付選択後、日付をクリア、再度ドロップダウンカレンダーから値を選択した場合、選択した値(選択する日付は最初にドロップダウンカレンダーから選択した日付)がセットされません

と、このように管理人的にはあまり意図していない動きをしてくれます。
海外の人はこれで良いのかもしれませんが、日本で業務システムを作成する場合はおそらく NG として返されるのではないでしょうか?
管理人の知識が足りず、何かプロパティの設定が抜けているのが原因かもしれませんが、原因は特定できませんでした。

ただ、バインドしているオブジェクトへ初期値を設定すると下の fig.13 のように意図した通りに動作するようなので、Nullable<DateTime> が問題なのかもしれません。
上の fig.12 の場合でも一度値を確定してフォーカスを移した後は、想定した動作になっています。

fig.13 DateTimePicker にバインドする値へ初期値を設定

つまり、Extended WPF Toolkit の DateTimePicker を使用する場合、初期値 null は避けて使用する方が良さそうです。

ただ、WPF 標準の DatePicker、Extended WPF Toolkit の DateTimePicker どちらの場合も初回ロストフォーカス時は Validation が無視されているようなので、使用には注意が必要だと感じました。
未入力のまま他コントロールへフォーカスを移して Validation が実行されないのは、恐らく ReactiveProperty 初期化時の mode パラメータに初回 Validation を無視する指定をしているのが原因と考えられるので悩ましい所です。

とりあえずサンプルアプリで使っている WPF 標準のコントロールを Extended WPF Toolkit のコントロールに置き換えますが、管理人的に無効になる文字は入力できない方が UX 的に使い易いと考えているので、実際にアプリを作成する場合はやはりカスタムコントロールを作成すると思いますが、カスタムコントロールの作成については又の機会にして、モデルの複数項目を使用した Validation について紹介します。

 

… と本来であればこのまま続けて次章を書くんですが、今回は事情があって 2 回に分割しますので、引き続き読んでいただける場合は【WPF episode: 9′ ~ ReactiveProperty の Validation は DataAnnotation じゃないと思った?Ⅱ~】へ進んでください。

事情と言っても大した事情ではないんですが、このサイトでソースコードを表示するために使っている WordPress の Crayon Syntax Highlighter プラグインと広告を表示するための Advanced Ads プラグインが競合しているようで、ソースコードを 1 エントリ内に一定数以上貼る (?) と、以降の本文が表示されない(Crayon Syntax Highlighter プラグインのソースコードだけは表示される)という現象に悩まされていて、現時点では解決方法が分からない為、苦肉の策として 2 回に分けて公開することにしました。

回避策等が分かれば、将来的に 2 回分のエントリをマージするかもしれませんが、とりあえず今の所は 2 つのエントリとして公開します。

では、引き続き【WPF Prism episode: 9′ ~ ReactiveProperty の Validation は DataAnnotation じゃないと思った?Ⅱ~】をご覧ください。

 

あわせて読みたい

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

%d人のブロガーが「いいね」をつけました。