INotifyDataErrorInfoを使用したデータ検証
これまで例外を使用したデータ検証とIDataErrorInfoを使用したデータ検証を行いましたが、今回はWPF4.5で加わったINotifyDataErrorInfoを使用したデータ検証を行ってみます。
IDataErrorInfoを使用したデータ検証と似ていますが、INotifyDataErrorInfoを使用した場合以下の点が異なります。
・HasErrorプロパティを持っている。
・エラー情報の変更をイベントで通知する。
・1つのプロパティに対して複数のエラーメッセージを返すことができる。
・エラー情報はstring型以外でもOK。
それでは、ViewModelのサンプルコードを見てみましょう。
こんな感じで複数のエラーメッセージを表示できます。
IDataErrorInfoとINotifyDataErrorInfoどちらを使うかは人それぞれだと思うので、使いやすい方を使えばよいかなと思います。
WPFでデータ検証を行う方法は、これまでに説明した3種類のどれかを使用することになると思います。
データ検証はModelで行ったほうが良いのではないかと考える人も多いと思います。ただWPFのデータ検証機能が見ての通りViewModelよりなので、若干の違和感を感じますがViewModelでデータ検証を行っています。
次回はDataAnnotationsを利用して、もう少し楽にデータ検証を行う方法を紹介します。
using Livet; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; namespace Validation3.ViewModels { public class MainWindowViewModel : ViewModel, INotifyDataErrorInfo { private Dictionary<string, string[]> Errors = new Dictionary<string, string[]>(); public MainWindowViewModel() { Value1 = 0; Value2 = ""; } #region Value1変更通知プロパティ private int _Value1; public int Value1 { get { return _Value1; } set { if (_Value1 == value) return; _Value1 = value; // データ検証 Errors["Value1"] = value < 0 ? new[] { "0以上の数値を入力してください。" } : null; RaiseErrorsChanged(); // エラー情報が変更されたことを通知 RaisePropertyChanged(); } } #endregion #region Value2変更通知プロパティ private string _Value2; public string Value2 { get { return _Value2; } set { if (_Value2 == value) return; _Value2 = value; // データ検証 Errors["Value2"] = string.IsNullOrWhiteSpace(value) ? new[] { "値が入力されていません。", "何か入力してください。" } : null; RaiseErrorsChanged(); RaisePropertyChanged(); } } #endregion // エラー情報が変更された際に発生するイベント public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; // ErrorsChangedイベントを発生させる protected virtual void RaiseErrorsChanged([CallerMemberName]string propertyName = "") { var h = this.ErrorsChanged; if(h != null) { h(this, new DataErrorsChangedEventArgs(propertyName)); } } // エラーメッセージを取得する public System.Collections.IEnumerable GetErrors(string propertyName) { if (string.IsNullOrWhiteSpace(propertyName)) return null; if (!Errors.ContainsKey(propertyName)) return null; return Errors[propertyName]; } // エラーの有無 public bool HasErrors { get { return Errors.Values.Any(x => x != null); } } } }INotifyDataErrorInfoではErrorsChangedイベントでエラー情報の変更を通知します。 このイベントではどのプロパティのエラー情報が変更されたかが通知されます。 エラーメッセージはGetErrorsメソッドによって取得されます。このメソッドの戻り値はIEnumerable型なので、string以外のエラー情報を返すことも可能です。(今回はstring型で返していますが) IDataErrorInfoには無かったHasErrorプロパティも実装されています。このプロパティによってエラーの有無を取得することが可能になっています。 エラー情報の受け取り方がIDataErrorInfoと異なっていますが、データ検証の方法はそれほど変わりません。 今回のコードも、プロパティのsetterでデータ検証を行い、エラーメッセージをErrorsに格納しています。 エラー情報が複数あっても良いのでErrorsのValueはstring配列にしています。 そして、エラー情報が変更されたことを通知するためにErrorsChangedイベントを発生させています。 今回はValue1では今までと同じように1つのエラーメッセージを返していますが、Value2では試しに2つのエラーメッセージを返しています。 次にView側のコードです。
<Window x:Class="Validation3.Views.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" xmlns:l="http://schemas.livet-mvvm.net/2011/wpf" xmlns:v="clr-namespace:Validation3.Views" xmlns:vm="clr-namespace:Validation3.ViewModels" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <vm:MainWindowViewModel/> </Window.DataContext> <Window.Resources> <ControlTemplate x:Key="ErrorTemplate"> <DockPanel> <ItemsControl DockPanel.Dock="Right" Margin="5,0" ItemsSource="{Binding ElementName=adornedElement, Path=AdornedElement.(Validation.Errors)}"> <ItemsControl.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding ErrorContent}" Foreground="Red"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> <Border BorderBrush="Red" BorderThickness="1" Width="{Binding ElementName=adornedElement, Path=ActualWidth}" Height="{Binding ElementName=adornedElement, Path=ActualHeight}"> <AdornedElementPlaceholder Name="adornedElement"/> </Border> </DockPanel> </ControlTemplate> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="15"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="100"/> </Grid.ColumnDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" Text="数値:"/> <TextBox Grid.Row="0" Grid.Column="1" Validation.ErrorTemplate="{StaticResource ErrorTemplate}" Text="{Binding Value1, ValidatesOnNotifyDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/> <TextBlock Grid.Row="2" Grid.Column="0" Text="文字列:"/> <TextBox Grid.Row="2" Grid.Column="1" Validation.ErrorTemplate="{StaticResource ErrorTemplate}" Text="{Binding Value2, UpdateSourceTrigger=PropertyChanged}"/> </Grid> </Window>INotifyDataErrorInfoの場合もエラーメッセージをエラーテンプレートを使用して表示しますが、エラーメッセージが複数あることが前提なので前回のコードとは少し異なります。 エラーメッセージが複数あるのでItemsControlを使ってエラーメッセージを表示しています。 バインディングの際には「ValidatesOnNotifyDataErrors」をtrueにします。ただ、ValidatesOnNotifyDataErrorsはデフォルトでtrueになっているので省略することが可能です。(Value1では説明のために記載していますが、Value2では省略しています) このコードを実行し、エラーを発生させると以下の様になります。

スポンサーサイト